├── .angular-cli.json
├── .gitignore
├── .travis.yml
├── .vscode
└── launch.json
├── ANA-CHAT-SERVER-SETUP-README.md
├── LICENSE
├── README.md
├── appveyor.yml
├── build
└── config.gypi
├── deploy.sh
├── e2e
├── app.e2e-spec.ts
├── app.po.ts
└── tsconfig.e2e.json
├── karma.conf.js
├── npm-shrinkwrap.json
├── pack-ana-studio.js
├── package.json
├── protractor.conf.js
├── src
├── app
│ ├── app.component.css
│ ├── app.component.html
│ ├── app.component.ts
│ ├── app.module.ts
│ ├── components
│ │ ├── analytics
│ │ │ ├── analytics-frame
│ │ │ │ ├── analytics-frame.component.css
│ │ │ │ ├── analytics-frame.component.html
│ │ │ │ └── analytics-frame.component.ts
│ │ │ └── analytics.module.ts
│ │ ├── deploy
│ │ │ ├── deploy.module.ts
│ │ │ └── landing
│ │ │ │ └── deploy-landing
│ │ │ │ ├── deploy-landing.component.css
│ │ │ │ ├── deploy-landing.component.html
│ │ │ │ └── deploy-landing.component.ts
│ │ ├── home
│ │ │ ├── home.component.css
│ │ │ ├── home.component.html
│ │ │ └── home.component.ts
│ │ ├── manage-users
│ │ │ ├── biz-accounts
│ │ │ │ ├── biz-accounts.component.css
│ │ │ │ ├── biz-accounts.component.html
│ │ │ │ └── biz-accounts.component.ts
│ │ │ ├── manage-users.module.ts
│ │ │ └── users
│ │ │ │ ├── users.component.css
│ │ │ │ ├── users.component.html
│ │ │ │ └── users.component.ts
│ │ ├── shared
│ │ │ ├── ana-cloud-signup
│ │ │ │ ├── ana-cloud-signup.component.css
│ │ │ │ ├── ana-cloud-signup.component.html
│ │ │ │ └── ana-cloud-signup.component.ts
│ │ │ ├── app-header-bar
│ │ │ │ ├── app-header-bar.component.css
│ │ │ │ ├── app-header-bar.component.html
│ │ │ │ └── app-header-bar.component.ts
│ │ │ ├── business-picker
│ │ │ │ ├── business-picker.component.css
│ │ │ │ ├── business-picker.component.html
│ │ │ │ └── business-picker.component.ts
│ │ │ ├── change-password
│ │ │ │ ├── change-password.component.css
│ │ │ │ ├── change-password.component.html
│ │ │ │ └── change-password.component.ts
│ │ │ ├── chat-server-manager
│ │ │ │ ├── chat-server-manager.component.css
│ │ │ │ ├── chat-server-manager.component.html
│ │ │ │ └── chat-server-manager.component.ts
│ │ │ ├── create-chatbot
│ │ │ │ ├── create-chatbot.component.css
│ │ │ │ ├── create-chatbot.component.html
│ │ │ │ └── create-chatbot.component.ts
│ │ │ ├── create-user
│ │ │ │ ├── create-user.component.css
│ │ │ │ ├── create-user.component.html
│ │ │ │ └── create-user.component.ts
│ │ │ ├── edit-business-account
│ │ │ │ ├── edit-business-account.component.css
│ │ │ │ ├── edit-business-account.component.html
│ │ │ │ └── edit-business-account.component.ts
│ │ │ ├── get-ana-chat-server
│ │ │ │ ├── get-ana-chat-server.component.css
│ │ │ │ ├── get-ana-chat-server.component.html
│ │ │ │ └── get-ana-chat-server.component.ts
│ │ │ ├── info-dialog
│ │ │ │ ├── info-dialog.component.css
│ │ │ │ ├── info-dialog.component.html
│ │ │ │ └── info-dialog.component.ts
│ │ │ ├── loading-indicator
│ │ │ │ ├── loading-indicator.component.css
│ │ │ │ ├── loading-indicator.component.html
│ │ │ │ └── loading-indicator.component.ts
│ │ │ ├── loading-mask
│ │ │ │ ├── loading-mask.component.css
│ │ │ │ ├── loading-mask.component.html
│ │ │ │ └── loading-mask.component.ts
│ │ │ ├── login
│ │ │ │ ├── login.component.css
│ │ │ │ ├── login.component.html
│ │ │ │ └── login.component.ts
│ │ │ ├── publish-chatbot
│ │ │ │ ├── publish-chatbot.component.css
│ │ │ │ ├── publish-chatbot.component.html
│ │ │ │ └── publish-chatbot.component.ts
│ │ │ ├── publish-dialog
│ │ │ │ ├── publish-dialog.component.css
│ │ │ │ ├── publish-dialog.component.html
│ │ │ │ └── publish-dialog.component.ts
│ │ │ └── update-password
│ │ │ │ ├── update-password.component.css
│ │ │ │ ├── update-password.component.html
│ │ │ │ └── update-password.component.ts
│ │ └── studio
│ │ │ ├── chatflow
│ │ │ ├── chatflow.component.css
│ │ │ ├── chatflow.component.html
│ │ │ └── chatflow.component.ts
│ │ │ ├── landing
│ │ │ ├── landing.component.css
│ │ │ ├── landing.component.html
│ │ │ └── landing.component.ts
│ │ │ ├── nodeeditor
│ │ │ ├── nodeeditor.component.css
│ │ │ ├── nodeeditor.component.html
│ │ │ └── nodeeditor.component.ts
│ │ │ ├── simulator-frame
│ │ │ ├── simulator-frame.component.css
│ │ │ ├── simulator-frame.component.html
│ │ │ └── simulator-frame.component.ts
│ │ │ └── studio.module.ts
│ ├── directives
│ │ └── autofocus.directive.ts
│ ├── models
│ │ ├── ana-chat.models.ts
│ │ ├── app.models.ts
│ │ ├── chatflow.models.ts
│ │ └── data.models.ts
│ ├── pipes
│ │ └── ellipsis.pipe.ts
│ ├── services
│ │ ├── analytics-window.service.ts
│ │ ├── chatflow.service.ts
│ │ ├── data.service.ts
│ │ ├── globals.service.ts
│ │ ├── info-dialog.service.ts
│ │ ├── login.service.ts
│ │ ├── settings.service.ts
│ │ └── simulator.service.ts
│ └── shared.module.ts
├── assets
│ ├── .gitkeep
│ ├── fonts
│ │ ├── Material_Icons_400_normal.woff
│ │ ├── Open_Sans_400_normal.svg
│ │ ├── Open_Sans_400_normal.ttf
│ │ ├── Open_Sans_400_normal.woff
│ │ ├── material-icons-fonts.css
│ │ └── open-sans-font.css
│ └── img
│ │ ├── ana.svg
│ │ ├── loading.svg
│ │ └── node-editor
│ │ ├── carousel-hover.png
│ │ ├── carousel.png
│ │ ├── gif-hover.png
│ │ ├── gif.png
│ │ ├── image-hover.png
│ │ ├── image.png
│ │ ├── text-hover.png
│ │ ├── text.png
│ │ ├── video-hover.png
│ │ └── video.png
├── electron.js
├── environments
│ ├── environment.local.ts
│ ├── environment.prod.ts
│ └── environment.ts
├── favicon.icns
├── favicon.ico
├── favicon
│ └── 256x256.png
├── index.html
├── main.ts
├── package-lock.json
├── package.json
├── polyfills.ts
├── splash.html
├── styles.css
├── test.ts
├── theme.scss
├── tsconfig.app.json
├── tsconfig.spec.json
└── typings.d.ts
├── tsconfig.json
└── tslint.json
/.angular-cli.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3 | "project": {
4 | "name": "ana-studio-web"
5 | },
6 | "apps": [
7 | {
8 | "root": "src",
9 | "outDir": "dist",
10 | "assets": [
11 | "assets",
12 | "favicon.ico",
13 | "favicon.icns",
14 | "favicon",
15 | "package.json",
16 | "electron.js",
17 | "splash.html"
18 | ],
19 | "index": "index.html",
20 | "main": "main.ts",
21 | "polyfills": "polyfills.ts",
22 | "test": "test.ts",
23 | "tsconfig": "tsconfig.app.json",
24 | "testTsconfig": "tsconfig.spec.json",
25 | "prefix": "app",
26 | "styles": [
27 | "../node_modules/highlightjs/styles/atom-one-dark.css",
28 | "theme.scss",
29 | "styles.css"
30 | ],
31 | "scripts": [
32 | "../node_modules/highlightjs/highlight.pack.min.js"
33 | ],
34 | "environmentSource": "environments/environment.ts",
35 | "environments": {
36 | "dev": "environments/environment.ts",
37 | "prod": "environments/environment.prod.ts",
38 | "local": "environments/environment.local.ts"
39 | }
40 | }
41 | ],
42 | "e2e": {
43 | "protractor": {
44 | "config": "./protractor.conf.js"
45 | }
46 | },
47 | "lint": [
48 | {
49 | "project": "src/tsconfig.app.json",
50 | "exclude": "**/node_modules/**"
51 | },
52 | {
53 | "project": "src/tsconfig.spec.json",
54 | "exclude": "**/node_modules/**"
55 | },
56 | {
57 | "project": "e2e/tsconfig.e2e.json",
58 | "exclude": "**/node_modules/**"
59 | }
60 | ],
61 | "test": {
62 | "karma": {
63 | "config": "./karma.conf.js"
64 | }
65 | },
66 | "defaults": {
67 | "styleExt": "css",
68 | "component": {
69 | "spec": false
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # compiled output
4 | /dist
5 | /out
6 | /tmp
7 | /out-tsc
8 | /release
9 | # dependencies
10 | /node_modules
11 | /src/node_modules
12 |
13 | # IDEs and editors
14 | /.idea
15 | .project
16 | .classpath
17 | .c9/
18 | *.launch
19 | .settings/
20 | *.sublime-workspace
21 |
22 | # IDE - VSCode
23 | .vscode/*
24 | !.vscode/settings.json
25 | !.vscode/tasks.json
26 | !.vscode/launch.json
27 | !.vscode/extensions.json
28 |
29 | # misc
30 | /.sass-cache
31 | /connect.lock
32 | /coverage
33 | /libpeerconnection.log
34 | npm-debug.log
35 | testem.log
36 | /typings
37 | yarn-error.log
38 |
39 | # e2e
40 | /e2e/*.js
41 | /e2e/*.map
42 |
43 | # System Files
44 | .DS_Store
45 | Thumbs.db
46 |
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | // Use IntelliSense to learn about possible Node.js debug attributes.
3 | // Hover to view descriptions of existing attributes.
4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
5 | "version": "0.2.0",
6 | "configurations": [
7 | {
8 | "name": "Launch Client",
9 | "type": "chrome",
10 | "request": "launch",
11 | "url": "http://localhost:4200/",
12 | "port": 9223,
13 | "sourceMaps": true,
14 | "trace": true,
15 | "webRoot": "${workspaceRoot}",
16 | "runtimeArgs": [
17 | "--disable-session-crashed-bubble",
18 | "--disable-infobars"
19 | ],
20 | "userDataDir": "${workspaceRoot}/.vscode/chrome"
21 | }
22 | ]
23 | }
--------------------------------------------------------------------------------
/ANA-CHAT-SERVER-SETUP-README.md:
--------------------------------------------------------------------------------
1 | # Ana Chat Server Setup Guide
2 |
3 | Ana Server is required to publish chat flows and serve published content across differant channels including Web, Android, iOS and Facebook (and many more to come soon). Ana server is decomposed into microservices, organized around certain business domains.
4 |
5 | Following are the required microservices to run Ana server
6 |
7 | 1) service-registry
8 | 2) config-server
9 | 3) api-gateway
10 | 4) ws-customers
11 | 5) business-service
12 | 6) user-service
13 | 7) core
14 | 8) history service (optional)
15 | 9) fcm-plugin (optional)
16 | 10) agents-service (optional)
17 | 11) file-service (optional)
18 | 12) analytics (optional)
19 |
20 | ## Installation
21 |
22 | ### Development mode
23 |
24 | **System Requirements**
25 |
26 | 1. Ubuntu Machine (Will be windows desktop/server and mac compatible very soon)
27 | 2. 8GB+ RAM
28 | 3. 2+ CPU Cores
29 | 4. 10 GB+ free disk space
30 |
31 | **Software Requirements**
32 |
33 | 1. Docker and docker compose installed.
34 | - Click [here](https://docs.docker.com/engine/installation/linux/docker-ce/ubuntu/) for docker installation guidelines
35 | - Click [here](https://docs.docker.com/compose/install/) for docker-compose installation guidelines
36 |
37 | #### Setup Steps
38 | 1. Download the docker-compose.yml file using the following command
39 | ```bash
40 | wget https://gist.githubusercontent.com/menneni/70ea1a174961f653cd62c2ae3b22ec1b/raw/docker-compose.yml
41 | ```
42 | 3. Pull and fire up the dockers using following commands
43 | ```bash
44 | docker-compose pull
45 | docker-compose build
46 | docker-compose up -d
47 | #### Important endpoints
48 | - http://localhost:80 - eureka dashboard/service-registry
49 | - http://localhost:8080 - api-gateway
50 | - http://localhost:8888 - config-server
51 | - http://localhost:9500 - core
52 | - http://localhost:8088 - ws-customers
53 | - http://localhost:8087 - business-service
54 | - http://localhost:8089 - user-service
55 |
56 | #### Notes
57 |
58 | Services running with spring boot requires already running service-registry and config-server for startup. docker-compose starts all microservices simultaniously but ensures dependencies using `depends_on` and `health check` option.
59 |
60 | Health check for core service - http://localhost:9500/bot/health
61 |
62 | Also service discovery mechanism needs some time after all applications startup. To ensure all required microservices for Ana server are up and running, check eureka dashboard (http://localhost:80), it should show all the required microservices instances in the eureka dashboard.
63 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Project Ana - Open Source Conversation Suite
2 |
3 | [](http://www.gnu.org/licenses/gpl-3.0)
4 | [](https://travis-ci.org/Kitsune-tools/ProjectAna)
5 | [](https://ci.appveyor.com/project/ana-developers/projectana)
6 | [](https://www.codacy.com/app/ana-developers/ProjectAna?utm_source=github.com&utm_medium=referral&utm_content=Kitsune-tools/ProjectAna&utm_campaign=Badge_Grade)
7 |
8 |
9 | World's first Open Source Conversation Platform which comes with a Graphical Chat Flow Designer and Chat Simulator. Supported channels are Web Chat, Android, iOS and Facebook Messenger.
10 |
11 | ## The Ana app
12 |
13 | A single cross-platform app where you can design and publish Ana chatbots, manage users and check the chatbot analytics.
14 |
15 | ## Installation
16 |
17 | Download The Ana app from https://www.ana.chat/downloads.html
18 |
19 | ## How to use Ana app?
20 |
21 | The goal of The Ana app is to make it easier to build chat flows without much technical knowledge, it even allows you to create and test the bot while designing, using the simulator.
22 |
23 | First, you need to create your bot using the studio in The Ana app. After finalizing the desired chat flow, you can publish it to The Ana server and integrate it with various channels like [Web](https://github.com/Kitsune-tools/ANAChat-Web), [Android](https://github.com/Kitsune-tools/ANAChat-Android), [iOS](https://github.com/Kitsune-tools/ANAChat-iOS) and Facebook. Ana server is responsible for serving published chatbot across different channels.
24 |
25 | ## Creating a bot and testing using the simulator
26 |
27 | 1. Open Ana app and Click on `Studio`
28 | 2. Click on `Add new chatbot` to create a new project or 'Import chatbot' to import an existing project
29 | 3. Enter the name of the new project and submit
30 | 4. Create the desired chat flow and save it
31 | 5. Click on `RUN CHAT` option to test chat flow locally using the simulator
32 |
33 | ## Publishing bot to Ana server
34 |
35 | 1. Ensure Ana server is up and running (Instructions for [Ana server](https://github.com/Kitsune-tools/ProjectANA/blob/master/ANA-CHAT-SERVER-SETUP-README.md) setup)
36 | 2. Open Ana studio and configure Ana server URL
37 | 1. Click on `login`
38 | 2. Click on `Add Ana chat server connection`
39 | 3. If it doesn't find any saved Ana chat server connections, you will be prompted to add new server connection, Click on `Add Ana chat server connection`
40 | 3. Click on the drop-down, enter the server name and Ana api-gateway URL (ex: http://localhost:8080)
41 | 4. Save changes
42 | 5. Click on login, select recently added Ana server connection and enter username as 'Admin' and password as 'ana123' and submit
43 | 6. Now we have integrated Ana studio with Ana server successfully. Next, we need to create a business and submit flow to Ana server
44 | 3. Now open the studio from home page and create a new project and publish chat flow or import an existing chat flow
45 |
46 | ## Deploying your Ana chatbot
47 |
48 | The following channels are supported for deploying your Ana chatbot. Follow the links for instructions.
49 | 1. [Web Chat](https://github.com/Kitsune-tools/ANAChat-Web)
50 | 2. [Android](https://github.com/Kitsune-tools/ANAChat-Android)
51 | 3. [iOS](https://github.com/Kitsune-tools/ANAChat-iOS)
52 | 4. Facebook Messenger (Coming soon...)
53 |
54 | ## Note
55 |
56 | This is just the setup documentation. ANA Conversation Suite has much more scope than predefined flows. You can call your own APIs from the chat, capture and save information from the user. You can take different types of inputs like button clicks, text, image, video etc. For now, explore the code to get to know all the possibilities.
57 |
58 | ## License
59 |
60 | Ana Conversation Suite is available under the [GNU GPLv3 license](https://www.gnu.org/licenses/gpl-3.0.en.html).
61 |
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | version: 1.0.{build}
2 |
3 | environment:
4 | nodejs_version: "9.5.0"
5 | s3-bucket-path:
6 | secure: LYtUdyCOp3BE/HnR4JrMNGF8xbDDY+5h1qK7GCXgQBUC9cnkGtHU4apkryIc4d/j
7 | s3-bucket-name:
8 | secure: u4qHUlDVT5jyK3hIqNno4w==
9 | s3-folder-path:
10 | secure: weEMNt0NE85Ek4EFUmUnElnTcsKgA0qTnCOQIbpSb14=
11 |
12 | platform:
13 | - x64
14 |
15 | image: Visual Studio 2015
16 |
17 | cache:
18 | - packages -> **\packages.config # preserve "packages" directory in the root of build folder but will reset it if packages.config is modified
19 | - node_modules # local npm modules
20 | - "%APPDATA%\npm-cache" # npm cache
21 |
22 | build:
23 | parallel: true
24 | # Post-install test scripts.
25 | install:
26 | # Get the latest stable version of Node 0.STABLE.latest
27 | - ps: Install-Product node $env:nodejs_version
28 | - IF %nodejs_version% EQU 0.8 npm config set strict-ssl false
29 | - IF %nodejs_version% LSS 4 npm -g install npm@2
30 | - IF %nodejs_version% EQU 5 npm -g install npm@3
31 | - set PATH=%APPDATA%\npm;%PATH%
32 | - npm install
33 | - npm i -g @angular-devkit/core
34 | - npm install -g @angular/cli@1.6.1
35 | - npm i node-sass
36 | - mkdir dist2
37 | - ng build --prod --aot=false
38 | #Cloning other repos
39 | - git clone https://github.com/Kitsune-tools/Ana-Analytics-Dashboard.git
40 | - git clone https://github.com/Kitsune-tools/ANAChat-Web.git
41 | - rm -rf node_modules\
42 | #For analytics-dash
43 | - cd Ana-Analytics-Dashboard
44 | - npm install typings -g
45 | - npm i @angular-devkit/core
46 | - npm install -g @angular/cli@1.6.1
47 | - npm i node-sass
48 | - npm install
49 | - ng build --prod --aot=false
50 | - cd ..
51 | - mkdir dist2\analytics-dashboard
52 | - cp -R Ana-Analytics-Dashboard/dist/* dist2/analytics-dashboard/
53 | - ls -l
54 | - ls -l Ana-Analytics-Dashboard\
55 | #For Chat-Web
56 | - cd ANAChat-Web
57 | - npm i node-sass
58 | - npm install
59 | - ng build --prod --aot=false
60 | - cd ..
61 | - mkdir dist2\simulator # For web chat
62 | - ls -l ANAChat-Web\
63 | - cp -R ANAChat-Web/dist/* dist2/simulator/
64 | # For ana APP
65 | - npm cache verify
66 | - npm install -g npm@5.6.0
67 | - ls -l
68 | - ls -l
69 | - mkdir dist\simulator
70 | - mkdir dist\analytics-dashboard
71 | - cp -R dist2/simulator/* dist/simulator/
72 | - cp -R dist2/analytics-dashboard/* dist/analytics-dashboard/
73 | build_script:
74 | # - npm i @angular-devkit/core
75 | # - ng build --prod --aot=false
76 | - cd dist
77 | - ls -l
78 | - npm i
79 | - npm i -g electron-builder@19.56.0
80 | - npm i -g @angular-devkit/core
81 | - npm install -g electron-rebuild
82 | - electron-rebuild -v 1.8.2
83 | # - node_modules/.bin/electron-rebuild.cmd -v 1.8.2
84 | - electron-builder && cd ..
85 | # - aws s3api get-bucket-location --bucket %s3-bucket-name%
86 | - mkdir deploy-folder
87 | - cp release/Ana*.exe deploy-folder
88 | - cp release/Ana*.exe.blockmap deploy-folder
89 | - cp release/*.yml deploy-folder
90 | # CLI Upload to s3
91 | # - aws s3 cp release %s3-bucket-path%/win-ia32/ --exclude "win*" --include "Ana*.exe.blockmap" --recursive --acl public-read
92 | # - aws s3 cp release %s3-bucket-path%/win-ia32/ --exclude "win*" --include "*.yml" --recursive --acl public-read
93 | # - aws s3 cp release %s3-bucket-path%/win-ia32/ --exclude "win*" --include "Ana*.exe" --recursive --acl public-read
94 | # - ECHO "File upload to S3 Successfull 32 bit"
95 | # - aws s3 cp release %s3-bucket-path%/win-x64/ --exclude "win*" --include "Ana*.exe.blockmap" --recursive --acl public-read
96 | # - aws s3 cp release %s3-bucket-path%/win-x64/ --exclude "win*" --include "*.yml" --recursive --acl public-read
97 | # - aws s3 cp release %s3-bucket-path%/win-x64/ --exclude "win*" --include "Ana*.exe" --recursive --acl public-read
98 | # - ECHO "File upload to S3 Successfull 64 bit"
99 |
100 | deploy:
101 | # FTP deployment provider settings
102 | - provider: S3
103 | access_key_id: "%AWS_ACCESS_KEY_ID%"
104 | secret_access_key: "%AWS_SECRET_ACCESS_KEY%"
105 | bucket: "%s3-bucket-name%"
106 | region: ap-south-1
107 | set_public: true
108 | folder: s3-folder-path/win-x64
109 | artifact: deploy-folder
110 | on:
111 | branch:
112 | - release
113 | - release-beta
114 |
115 | # Amazon S3 deployment provider settings
116 | - provider: S3
117 | access_key_id: "%AWS_ACCESS_KEY_ID%"
118 | secret_access_key: "%AWS_SECRET_ACCESS_KEY%"
119 | bucket: "%s3-bucket-name%"
120 | region: ap-south-1
121 | set_public: true
122 | folder: s3-folder-path/win-ia32
123 | artifact: deploy-folder
124 | on:
125 | branch:
126 | - release
127 | - release-beta
128 |
129 |
130 | notifications:
131 | - provider: Email
132 | to:
133 | - soumyajit.dutta@nowfloats.com
134 | - khaja.nizamuddin@nowfloats.com
135 | on_build_success: false
136 | on_build_failure: false
137 | on_build_status_changed: true
138 |
--------------------------------------------------------------------------------
/build/config.gypi:
--------------------------------------------------------------------------------
1 | # Do not edit. File was generated by node-gyp's "configure" step
2 | {
3 | "target_defaults": {
4 | "cflags": [],
5 | "default_configuration": "Release",
6 | "defines": [],
7 | "include_dirs": [],
8 | "libraries": [],
9 | "msbuild_toolset": "v141",
10 | "msvs_windows_target_platform_version": "10.0.17763.0"
11 | },
12 | "variables": {
13 | "asan": 0,
14 | "build_v8_with_gn": "false",
15 | "coverage": "false",
16 | "debug_nghttp2": "false",
17 | "enable_lto": "false",
18 | "enable_pgo_generate": "false",
19 | "enable_pgo_use": "false",
20 | "force_dynamic_crt": 0,
21 | "host_arch": "x64",
22 | "icu_data_in": "..\\..\\deps/icu-small\\source/data/in\\icudt64l.dat",
23 | "icu_endianness": "l",
24 | "icu_gyp_path": "tools/icu/icu-generic.gyp",
25 | "icu_locales": "en,root",
26 | "icu_path": "deps/icu-small",
27 | "icu_small": "true",
28 | "icu_ver_major": "64",
29 | "is_debug": 0,
30 | "napi_build_version": "5",
31 | "nasm_version": "2.14",
32 | "node_byteorder": "little",
33 | "node_code_cache": "yes",
34 | "node_debug_lib": "false",
35 | "node_enable_d8": "false",
36 | "node_install_npm": "true",
37 | "node_module_version": 72,
38 | "node_no_browser_globals": "false",
39 | "node_prefix": "/usr/local",
40 | "node_release_urlbase": "https://nodejs.org/download/release/",
41 | "node_report": "true",
42 | "node_shared": "false",
43 | "node_shared_cares": "false",
44 | "node_shared_http_parser": "false",
45 | "node_shared_libuv": "false",
46 | "node_shared_nghttp2": "false",
47 | "node_shared_openssl": "false",
48 | "node_shared_zlib": "false",
49 | "node_tag": "",
50 | "node_target_type": "executable",
51 | "node_use_bundled_v8": "true",
52 | "node_use_dtrace": "false",
53 | "node_use_etw": "true",
54 | "node_use_large_pages": "false",
55 | "node_use_large_pages_script_lld": "false",
56 | "node_use_node_snapshot": "true",
57 | "node_use_openssl": "true",
58 | "node_use_v8_platform": "true",
59 | "node_with_ltcg": "true",
60 | "node_without_node_options": "false",
61 | "openssl_fips": "",
62 | "openssl_is_fips": "false",
63 | "shlib_suffix": "so.72",
64 | "target_arch": "ia32",
65 | "v8_enable_gdbjit": 0,
66 | "v8_enable_i18n_support": 1,
67 | "v8_enable_inspector": 1,
68 | "v8_no_strict_aliasing": 1,
69 | "v8_optimized_debug": 1,
70 | "v8_promise_internal_field_count": 1,
71 | "v8_random_seed": 0,
72 | "v8_trace_maps": 0,
73 | "v8_use_siphash": 1,
74 | "v8_use_snapshot": 1,
75 | "want_separate_host_toolset": 0,
76 | "nodedir": "C:\\Users\\NF\\AppData\\Local\\node-gyp\\Cache\\12.12.0",
77 | "standalone_static_library": 1,
78 | "msbuild_path": "C:\\Program Files (x86)\\Microsoft Visual Studio\\2017\\BuildTools\\MSBuild\\15.0\\Bin\\MSBuild.exe"
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/deploy.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | if [ "$TRAVIS_BRANCH" == "release" || "$TRAVIS_BRANCH" == "release-beta"]; then
3 | # Linux upload
4 | if [ "$TRAVIS_OS_NAME" == "linux" ]; then which python; fi
5 | if [ "$TRAVIS_OS_NAME" == "linux" ]; then pip install awscli --upgrade --user; fi
6 | if [ "$TRAVIS_OS_NAME" == "linux" ]; then ls -al /usr/local/bin/python; fi
7 | if [ "$TRAVIS_OS_NAME" == "linux" ]; then export PATH=~/.local/bin:$PATH; fi
8 | if [ "$TRAVIS_OS_NAME" == "linux" ]; then source ~/.bash_profile; fi
9 | if [ "$TRAVIS_OS_NAME" == "linux" ]; then aws s3 cp *.AppImage s3://ana-content/cdn/dist/ana-app/linux-ia32/ --acl public-read; fi
10 | if [ "$TRAVIS_OS_NAME" == "linux" ]; then aws s3 cp *.AppImage s3://ana-content/cdn/dist/ana-app/linux-x64/ --acl public-read; fi
11 | if [ "$TRAVIS_OS_NAME" == "linux" ]; then aws s3 cp latest-linux.yml s3://ana-content/cdn/dist/ana-app/linux-ia32/ --acl public-read; fi
12 | if [ "$TRAVIS_OS_NAME" == "linux" ]; then aws s3 cp latest-linux.yml s3://ana-content/cdn/dist/ana-app/linux-x64/ --acl public-read; fi
13 | if [ "$TRAVIS_OS_NAME" == "linux" ]; then aws s3 ls s3://ana-content/cdn/dist/ana-app/linux-ia32/ --human-readable --summarize; fi
14 | if [ "$TRAVIS_OS_NAME" == "linux" ]; then aws s3 ls s3://ana-content/cdn/dist/ana-app/linux-x64/ --human-readable --summarize; fi
15 | # Mac only Upload to s3
16 | if [ "$TRAVIS_OS_NAME" == "osx" ]; then aws --version; fi
17 | if [ "$TRAVIS_OS_NAME" == "osx" ]; then which python; fi
18 | if [ "$TRAVIS_OS_NAME" == "osx" ]; then ls -al /usr/local/bin/python; fi
19 | if [ "$TRAVIS_OS_NAME" == "osx" ]; then export PATH=~/.local/bin:$PATH; fi
20 | if [ "$TRAVIS_OS_NAME" == "osx" ]; then source ~/.bash_profile; fi
21 | if [ "$TRAVIS_OS_NAME" == "osx" ]; then aws s3 cp *.dmg s3://ana-content/cdn/dist/ana-app/mac-x64/ --acl public-read; fi
22 | if [ "$TRAVIS_OS_NAME" == "osx" ]; then aws s3 cp *.dmg.blockmap s3://ana-content/cdn/dist/ana-app/mac-x64/ --acl public-read; fi
23 | if [ "$TRAVIS_OS_NAME" == "osx" ]; then aws s3 cp *.zip s3://ana-content/cdn/dist/ana-app/mac-x64/ --acl public-read; fi
24 | if [ "$TRAVIS_OS_NAME" == "osx" ]; then aws s3 cp latest-mac.json s3://ana-content/cdn/dist/ana-app/mac-x64/ --acl public-read; fi
25 | if [ "$TRAVIS_OS_NAME" == "osx" ]; then aws s3 cp latest-mac.yml s3://ana-content/cdn/dist/ana-app/mac-x64/ --acl public-read; fi
26 | if [ "$TRAVIS_OS_NAME" == "osx" ]; then aws s3 ls s3://ana-content/cdn/dist/ana-app/mac-x64/ --human-readable --summarize; fi
27 | fi
--------------------------------------------------------------------------------
/e2e/app.e2e-spec.ts:
--------------------------------------------------------------------------------
1 | import { AppPage } from './app.po';
2 |
3 | describe('ana-studio-web 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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/pack-ana-studio.js:
--------------------------------------------------------------------------------
1 | const shell = require('shelljs');
2 | let skipSimulator = (process.argv.indexOf('--skip-sim') != -1);
3 | let env = 'prod';
4 | if ((process.argv.indexOf('--dev') != -1)) {
5 | env = 'dev';
6 | } else if ((process.argv.indexOf('--local') != -1)) {
7 | env = 'local';
8 | }
9 |
10 | shell.echo(`Building for env: ${env} ...`);
11 |
12 | shell.rm('-R', 'src/node_modules'); //This will create problems with typescript compilation of angular app
13 |
14 | shell.echo('Building studio project...');
15 | shell.exec(`ng build ${env == 'prod' ? '--prod' : '--env=' + env} --aot=false`);
16 |
17 | shell.echo('Changing to simulator project...');
18 | if (shell.cd('../ana-web-chat/').code == 0) {
19 |
20 | if (!skipSimulator) {
21 | shell.echo('Building simulator project...');
22 | shell.exec(`ng build ${env == 'prod' ? '--prod' : ''}`);
23 | }
24 |
25 | shell.echo('Changing back to studio project...');
26 | shell.cd('../ana-studio-web/');
27 |
28 | if (!skipSimulator) {
29 | shell.echo('Copying simulator to studio dist..');
30 | shell.cp('-R', '../ana-web-chat/dist', 'dist/simulator');
31 |
32 | shell.echo('Updating simulator index.html base tag...');
33 | shell.sed('-i', '', '', 'dist/simulator/index.html');
34 | }
35 |
36 | if (shell.cd('../ana-analytics-dashboard/').code == 0) {
37 | shell.echo('Building analytics dashboard project...');
38 | shell.exec(`ng build ${env == 'prod' ? '--prod' : ''} --aot=false`);
39 |
40 | shell.echo('Changing back to studio project...');
41 | shell.cd('../ana-studio-web/');
42 |
43 | shell.echo('Copying analytics dashboard to studio dist..');
44 | shell.cp('-R', '../ana-analytics-dashboard/dist', 'dist/analytics-dashboard');
45 | } else {
46 | shell.echo('Project `ana-analytics-dashboard` not found! Make sure it is present adjacent to ana-studio-web. Skipping analytics dashboard!');
47 | }
48 |
49 | shell.echo('Packing Ana Studio into an electron app...');
50 | shell.rm('-R', 'release');
51 | shell.cd('dist');
52 | shell.exec('electron-builder');
53 | shell.cd('../dist');
54 | shell.echo('Done');
55 | } else {
56 | shell.echo('Project `ana-web-chat` not found! Make sure it is present adjacent to ana-studio-web.');
57 | shell.echo('Aborted');
58 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ana-conversation-studio",
3 | "version": "0.0.0",
4 | "license": "GNU-GPLv3",
5 | "scripts": {
6 | "ng": "ng",
7 | "start": "ng serve --port=4201 -o --env=local",
8 | "build": "ng build",
9 | "test": "ng test",
10 | "lint": "ng lint",
11 | "e2e": "ng e2e",
12 | "electron": "ng build --prod --aot=false && cd dist && electron-builder && \"../release/win-unpacked/Ana.exe\"",
13 | "pack": "node pack-ana-studio.js",
14 | "pack-dev": "node pack-ana-studio.js --dev",
15 | "pack-local": "node pack-ana-studio.js --local"
16 | },
17 | "private": true,
18 | "dependencies": {
19 | "@angular/animations": "~5.1.1",
20 | "@angular/cdk": "~5.0.2",
21 | "@angular/common": "~5.1.1",
22 | "@angular/compiler": "~5.1.1",
23 | "@angular/core": "~5.1.1",
24 | "@angular/forms": "~5.1.1",
25 | "@angular/http": "~5.1.1",
26 | "@angular/material": "~5.0.2",
27 | "@angular/platform-browser": "~5.1.1",
28 | "@angular/platform-browser-dynamic": "~5.1.1",
29 | "@angular/router": "~5.1.1",
30 | "angular2-highlight-js": "^6.0.1-alpha",
31 | "angular2-hotkeys": "^2.0.3",
32 | "bootstrap": "^3.3.7",
33 | "bson": "^1.0.4",
34 | "core-js": "^2.4.1",
35 | "electron": "~1.8.2-beta.3",
36 | "font-awesome": "^4.7.0",
37 | "highlightjs": "^9.10.0",
38 | "jsonpath": "^1.0.0",
39 | "lite-server": "^2.5.4",
40 | "mousetrap": "^1.6.1",
41 | "ngx-clipboard": "^10.0.0",
42 | "ngx-electron": "^1.0.4",
43 | "node-gyp": "^6.0.0",
44 | "node-sass": "^4.13.0",
45 | "rxjs": "^5.5.2",
46 | "ts-md5": "^1.2.4",
47 | "underscore": "^1.8.3",
48 | "zone.js": "^0.8.14"
49 | },
50 | "devDependencies": {
51 | "@angular/cli": "1.6.1",
52 | "@angular/compiler-cli": "~5.1.1",
53 | "@angular/language-service": "~5.1.1",
54 | "@types/bson": "^1.0.4",
55 | "@types/jasmine": "~2.5.53",
56 | "@types/jasminewd2": "~2.0.2",
57 | "@types/jsonpath": "^0.2.0",
58 | "@types/mousetrap": "^1.6.0",
59 | "@types/node": "~6.0.60",
60 | "@types/shelljs": "^0.7.6",
61 | "@types/underscore": "^1.8.5",
62 | "codelyzer": "^4.0.1",
63 | "electron-builder": "^19.56.0",
64 | "jasmine-core": "~2.8.0",
65 | "jasmine-spec-reporter": "~4.2.1",
66 | "karma": "~1.7.0",
67 | "karma-chrome-launcher": "~2.2.0",
68 | "karma-cli": "~1.0.1",
69 | "karma-coverage-istanbul-reporter": "^1.2.1",
70 | "karma-jasmine": "~1.1.0",
71 | "karma-jasmine-html-reporter": "^0.2.2",
72 | "protractor": "~5.1.2",
73 | "shelljs": "^0.7.8",
74 | "ts-node": "~3.3.0",
75 | "tslint": "~5.7.0",
76 | "typescript": "2.4.2"
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/app/app.component.css:
--------------------------------------------------------------------------------
1 | @media (max-width: 767px) {
2 | /* On small screens, the nav menu spans the full width of the screen. Leave a space for it. */
3 | .body-content {
4 | padding-top: 50px;
5 | }
6 | }
7 |
8 | .noselect {
9 | -webkit-touch-callout: none; /* iOS Safari */
10 | -webkit-user-select: none; /* Safari */
11 | -khtml-user-select: none; /* Konqueror HTML */
12 | -moz-user-select: none; /* Firefox */
13 | -ms-user-select: none; /* Internet Explorer/Edge */
14 | user-select: none; /* Non-prefixed version, currently
15 | supported by Chrome and Opera */
16 | }
17 |
18 | .top-nav {
19 | background-color: white;
20 | color: #8cc83c;
21 | }
22 |
23 | .page-content {
24 | display: flex;
25 | height: 100%;
26 | min-height: 95vh !important;
27 | }
28 |
29 | mat-sidenav-container {
30 | margin: 0;
31 | width: 100%;
32 | height: 100%;
33 | }
34 |
35 | mat-sidenav {
36 | width: 250px;
37 | background-color: #8cc83c !important;
38 | }
39 |
40 | li .glyphicon {
41 | margin-right: 10px;
42 | }
43 |
44 |
45 | @media (min-width: 768px) {
46 | .navbar {
47 | border-radius: 0px;
48 | border-width: 0px;
49 | }
50 |
51 | .navbar-header {
52 | float: left;
53 | width: auto;
54 | margin: 0 10px;
55 | }
56 |
57 | .navbar-collapse {
58 | padding: 0px;
59 | }
60 |
61 | .navbar ul {
62 | float: none;
63 | }
64 |
65 | .navbar li {
66 | float: none;
67 | font-size: 15px;
68 | transition: all .3s;
69 | }
70 |
71 | .navbar li a {
72 | padding: 10px 16px;
73 | color: white;
74 | transition: all .3s;
75 | }
76 |
77 | .navbar a {
78 | /* If a menu item's text is too long, truncate it */
79 | width: 100%;
80 | white-space: nowrap;
81 | overflow: hidden;
82 | text-overflow: ellipsis;
83 | }
84 | }
85 |
86 | .navbar-brand {
87 | color: white;
88 | }
89 |
90 | .navbar-btn {
91 | background-color: transparent;
92 | color: white;
93 | font-size: 24px;
94 | padding: 0;
95 | margin: 7px 0 10px 0;
96 | float: left;
97 | }
98 |
99 | .nav > li > a:hover,
100 | .nav > li > a:focus {
101 | text-decoration: none;
102 | background-color: #8cc83c !important;
103 | color: white !important;
104 | }
105 |
106 | .nav-menu-wrapper {
107 | position: fixed;
108 | top: 50px;
109 | left: 0;
110 | right: 0;
111 | z-index: 1;
112 | height: 100%;
113 | padding: 0px
114 | }
115 |
116 | .title-bar {
117 | position: fixed;
118 | top: 0;
119 | left: 0;
120 | right: 0;
121 | z-index: 1;
122 | height: 50px;
123 | padding: 0px
124 | }
125 |
126 | .nav-menu-item-active a {
127 | color: #8cc83c !important;
128 | background-color: white;
129 | }
130 |
131 | .nav-menu-item-active a :hover, .nav-menu-item-active a :active {
132 | color: lightgray !important;
133 | }
134 |
135 | .body-wrapper {
136 | position: fixed;
137 | top: 50px;
138 | right: 0;
139 | z-index: 1;
140 | height: 100%;
141 | padding: 0px;
142 | width: 100%;
143 | }
144 |
145 | .title-bar-header {
146 | margin: 0 0 5px 10px
147 | }
148 |
149 | .fullpage-loading-spinner {
150 | position: fixed;
151 | background-color: rgba(0, 0, 0, 0.6);
152 | width: 100%;
153 | top: 0;
154 | left: 0;
155 | height: 100%;
156 | }
157 |
158 | .fullpage-loading-spinner > mat-spinner {
159 | text-align: center;
160 | margin: auto;
161 | height: 100%;
162 | }
163 |
164 | .fullpage-loading-spinner > button {
165 | text-align: center;
166 | margin: 40px;
167 | height: 100%;
168 | color: white;
169 | }
170 |
171 | .navbar-actions {
172 | float: right;
173 | padding: 9px 0;
174 | margin-right: 10px;
175 | }
176 |
177 | .navbar-actions > button {
178 | background-color: #8cc83c;
179 | color: white;
180 | }
181 |
--------------------------------------------------------------------------------
/src/app/app.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
9 |
--------------------------------------------------------------------------------
/src/app/app.component.ts:
--------------------------------------------------------------------------------
1 | import { Component } from '@angular/core';
2 | import { Router } from '@angular/router';
3 | import { GlobalsService } from './services/globals.service';
4 | import { MatDialog, MatDialogRef, MAT_DIALOG_DATA, MatSnackBar } from '@angular/material';
5 |
6 | @Component({
7 | selector: 'app-root',
8 | templateUrl: './app.component.html',
9 | styleUrls: ['./app.component.css'],
10 | })
11 | export class AppComponent {
12 | constructor(
13 | public global: GlobalsService,
14 | public router: Router,
15 | public dialog: MatDialog) { }
16 |
17 | loading() {
18 | return this.global.loading;
19 | }
20 | hideLoading() {
21 | this.global.loading = false;
22 | }
23 |
24 | goto(path: string) {
25 | this.router.navigateByUrl(path);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/app/app.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core';
2 | import { Routes, RouterModule } from '@angular/router';
3 |
4 | import { AppComponent } from './app.component';
5 | import { HomeComponent } from './components/home/home.component';
6 | import { SharedModule } from './shared.module';
7 | import { ManageUsersModule, MANAGE_USERS_ROUTES } from './components/manage-users/manage-users.module';
8 | import { StudioModule, STUDIO_ROUTES } from './components/studio/studio.module';
9 | import { AnalyticsModule, ANALYTICS_ROUTES } from './components/analytics/analytics.module';
10 | import { HotkeyModule } from 'angular2-hotkeys';
11 | import { DeployModule, DEPLOY_ROUTES } from './components/deploy/deploy.module';
12 |
13 | const APP_ROUTES: Routes = [
14 | { path: '', redirectTo: 'home', pathMatch: "full" },
15 | {
16 | path: 'manage-users',
17 | children: MANAGE_USERS_ROUTES
18 | },
19 | {
20 | path: 'studio',
21 | children: STUDIO_ROUTES
22 | },
23 | {
24 | path: 'deploy',
25 | children: DEPLOY_ROUTES
26 | },
27 | {
28 | path: 'analytics',
29 | children: ANALYTICS_ROUTES
30 | },
31 | { path: 'home', component: HomeComponent },
32 | { path: '**', redirectTo: 'home' }
33 | ];
34 |
35 | @NgModule({
36 | declarations: [
37 | AppComponent,
38 | HomeComponent
39 | ],
40 | imports: [
41 | SharedModule,
42 | ManageUsersModule,
43 | StudioModule,
44 | AnalyticsModule,
45 | DeployModule,
46 | RouterModule.forRoot(APP_ROUTES, {
47 | useHash: true
48 | }),
49 | HotkeyModule.forRoot({
50 | cheatSheetCloseEsc: true,
51 | })
52 | ],
53 | bootstrap: [AppComponent]
54 | })
55 | export class AppModule { }
56 |
--------------------------------------------------------------------------------
/src/app/components/analytics/analytics-frame/analytics-frame.component.css:
--------------------------------------------------------------------------------
1 | .ana-analytics-iframe {
2 | position: fixed;
3 | left: 0;
4 | bottom: 0;
5 | right: 0;
6 | width: 100vw;
7 | height: calc(100vh - 70px);
8 | border: none;
9 | top: 70px;
10 | }
11 |
12 | :host {
13 | width: 100%;
14 | }
15 |
--------------------------------------------------------------------------------
/src/app/components/analytics/analytics-frame/analytics-frame.component.html:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/src/app/components/analytics/analytics-frame/analytics-frame.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, Injectable, ViewChild, ElementRef } from '@angular/core';
2 | import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
3 | import { GlobalsService } from '../../../services/globals.service';
4 | import { ActivatedRoute, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot, Router } from '@angular/router';
5 | import { DataService } from '../../../services/data.service';
6 | import { environment } from '../../../../environments/environment';
7 | import { AppHeaderBarComponent } from '../../shared/app-header-bar/app-header-bar.component';
8 | import { InfoDialogService } from '../../../services/info-dialog.service';
9 |
10 | @Component({
11 | selector: 'app-analytics-frame',
12 | templateUrl: './analytics-frame.component.html',
13 | styleUrls: ['./analytics-frame.component.css']
14 | })
15 | export class AnalyticsFrameComponent implements OnInit {
16 |
17 | ngAfterViewInit(): void {
18 | if (this.appHeader) {
19 | this.appHeader.goBack = () => {
20 | this.infoDialog.confirm('Go back to home page?', 'Are you sure you want to exit from analytics and go back to home page?', (ok) => {
21 | if (ok) {
22 | this.router.navigateByUrl('');
23 | }
24 | });
25 | };
26 | }
27 | }
28 |
29 | @ViewChild(AppHeaderBarComponent)
30 | appHeader: AppHeaderBarComponent;
31 |
32 | @ViewChild('analyticsFrame')
33 | analyticsFrame: ElementRef;
34 |
35 | iframeUrl: string | SafeUrl = "";
36 | constructor(
37 | private sanitizer: DomSanitizer,
38 | private globals: GlobalsService,
39 | private router: Router,
40 | private infoDialog: InfoDialogService,
41 | private route: ActivatedRoute) {
42 |
43 | this.iframeUrl = this.sanitizer.bypassSecurityTrustResourceUrl('');
44 |
45 | console.log('AnalyticsFrameComponent');
46 | this.route.queryParams.subscribe(x => {
47 | console.log('Params of analytics frame');
48 | console.log(x);
49 | if (x['apiBase'] && x['businessId'] && x['businessName'] && x['chatFlowId']) {
50 | let initialUrl = `/index.html#/?apiBase=${encodeURIComponent(x['apiBase'])}&businessId=${x['businessId']}&businessName=${encodeURIComponent(x['businessName'])}&chatFlowId=${x['chatFlowId']}`;
51 | let url = "analytics-dashboard" + initialUrl;
52 |
53 | if (environment.local)
54 | url = "http://localhost:4202" + initialUrl;
55 |
56 | console.log('Analytics Frame Url');
57 | console.log(url);
58 | this.iframeUrl = this.sanitizer.bypassSecurityTrustResourceUrl(url);
59 | }
60 | })
61 | }
62 |
63 | ngOnInit() {
64 | }
65 |
66 | }
67 |
68 | @Injectable()
69 | export class CanActivateAnalyticsFrameComponent implements CanActivate {
70 | constructor(
71 | private dataService: DataService) { }
72 |
73 | canActivate(
74 | route: ActivatedRouteSnapshot,
75 | state: RouterStateSnapshot) {
76 | return true; //this.dataService.isSuperAdmin()
77 | }
78 | }
--------------------------------------------------------------------------------
/src/app/components/analytics/analytics.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule, Injectable } from '@angular/core';
2 | import { RouterModule, Routes, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
3 | import { SharedModule } from '../../shared.module';
4 | import { AnalyticsFrameComponent, CanActivateAnalyticsFrameComponent } from './analytics-frame/analytics-frame.component';
5 | import { DataService } from '../../services/data.service';
6 |
7 | export const ANALYTICS_ROUTES: Routes = [
8 | {
9 | path: "",
10 | component: AnalyticsFrameComponent,
11 | canActivate: [CanActivateAnalyticsFrameComponent]
12 | }
13 | ];
14 |
15 | @NgModule({
16 | declarations: [
17 | AnalyticsFrameComponent
18 | ],
19 | imports: [
20 | SharedModule
21 | ],
22 | providers: [
23 | CanActivateAnalyticsFrameComponent
24 | ]
25 | })
26 | export class AnalyticsModule { }
--------------------------------------------------------------------------------
/src/app/components/deploy/deploy.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core';
2 | import { RouterModule, Routes } from '@angular/router';
3 | import { SharedModule } from '../../shared.module';
4 | import { DeployLandingComponent } from './landing/deploy-landing/deploy-landing.component';
5 | import { HighlightJsService } from 'angular2-highlight-js';
6 |
7 | export const DEPLOY_ROUTES: Routes = [
8 | {
9 | path: "",
10 | component: DeployLandingComponent
11 | }
12 | ];
13 |
14 | @NgModule({
15 | declarations: [
16 | DeployLandingComponent
17 | ],
18 | entryComponents: [
19 |
20 | ],
21 | imports: [
22 | SharedModule
23 | ],
24 | schemas: [
25 |
26 | ],
27 | providers: [
28 | HighlightJsService
29 | ]
30 | })
31 | export class DeployModule { }
32 |
33 |
--------------------------------------------------------------------------------
/src/app/components/deploy/landing/deploy-landing/deploy-landing.component.css:
--------------------------------------------------------------------------------
1 | :host {
2 | width: 100%;
3 | }
4 |
5 | mat-form-field, mat-select {
6 | width: 100%;
7 | }
8 |
9 | .bottom-actions > button {
10 | margin: 20px 10px;
11 | }
12 |
13 | button[color="primary"] {
14 | color: white;
15 | }
16 |
17 | mat-action-row > button {
18 | margin: 10px;
19 | }
20 |
21 | .bottom-actions {
22 | display: block;
23 | text-align: center;
24 | }
25 |
26 | .list-action-btn {
27 | float: right;
28 | }
29 |
30 | .list-item {
31 | width: 100%
32 | }
33 |
34 | .mat-expansion-panel-body {
35 | display: none !important;
36 | }
37 |
38 | .app-content {
39 | width: 80%;
40 | margin-left: auto;
41 | margin-right: auto;
42 | margin-top: 0;
43 | padding-bottom: 40px;
44 | }
45 |
46 | .page-title {
47 | display: flex;
48 | flex-direction: row;
49 | }
50 |
51 | .page-title .text {
52 | flex-grow: 1;
53 | }
54 |
55 | .page-title-actions button:first-child {
56 | margin-left: 10px;
57 | margin-right: 10px;
58 | }
59 |
60 | .table-body {
61 | padding: 10px 0;
62 | }
63 |
64 | section.control {
65 | margin-top: 15px;
66 | margin-bottom: 15px;
67 | }
68 |
69 | .radio-grp {
70 | margin-top: 0 !important;
71 | margin-bottom: 20px !important;
72 | }
73 |
74 | .left-header {
75 | margin-bottom: 19px;
76 | margin-top: 19px;
77 | margin-left: -2px;
78 | }
79 |
80 | label {
81 | font-weight: normal;
82 | font-size: small;
83 | opacity: 0.9;
84 | }
85 |
86 | .highlight-container {
87 | margin-bottom: 10px;
88 | }
89 |
90 | .highlight {
91 | border-radius: 3px;
92 | }
93 |
--------------------------------------------------------------------------------
/src/app/components/home/home.component.css:
--------------------------------------------------------------------------------
1 | .button-row {
2 | display: flex;
3 | align-items: center;
4 | justify-content: center;
5 | width: 100%;
6 | }
7 |
8 | .container {
9 | padding: 30px;
10 | width: 100vw;
11 | }
12 |
13 | .button-row > .mat-raised-button {
14 | margin-left: 10px;
15 | margin-right: 10px;
16 | margin-top: 10px;
17 | margin-bottom: 10px;
18 | height: 180px;
19 | width: 200px;
20 | font-size: 18px;
21 | }
22 |
23 | .btn-studio svg {
24 | width: 84px;
25 | height: 84px;
26 | margin-left: 4px;
27 | }
28 |
29 | .btn-user-management svg {
30 | width: 84px;
31 | height: 84px;
32 | margin-left: 4px;
33 | }
34 |
35 | .feature-button {
36 | color: #8cc83c !important;
37 | background-color: white !important;
38 | }
39 |
40 | .feature-button svg path {
41 | fill: #8cc83c !important;
42 | }
43 |
--------------------------------------------------------------------------------
/src/app/components/home/home.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, AfterViewInit } from '@angular/core';
2 | import { Router } from '@angular/router';
3 | import { LoginService } from '../../services/login.service';
4 | import { DataService } from '../../services/data.service';
5 | import { InfoDialogService } from '../../services/info-dialog.service';
6 | import { AnalyticsWindowService } from '../../services/analytics-window.service';
7 | import { BusinessPickerComponent, BusinessPickerParam, ChoosenBizAccChatProj } from '../shared/business-picker/business-picker.component';
8 |
9 | import { MatDialog } from '@angular/material';
10 |
11 | @Component({
12 | selector: 'app-home',
13 | templateUrl: './home.component.html',
14 | styleUrls: ['./home.component.css']
15 | })
16 | export class HomeComponent {
17 | constructor(
18 | private router: Router,
19 | private loginService: LoginService,
20 | private infoDialog: InfoDialogService,
21 | private dialog: MatDialog,
22 | private dataService: DataService) {
23 | }
24 |
25 | studio() {
26 | this.router.navigateByUrl('/studio');
27 | }
28 |
29 | userManagement() {
30 | this.loginService.performLogin(true, "/", true, (done) => {
31 | if (!done) {
32 | this.infoDialog.alert("Login Required", "You must be logged in to your Ana chat server to manage users");
33 | return;
34 | }
35 | if (this.dataService.loggedInUser) {
36 | if (this.dataService.isSuperAdmin()) {
37 | this.router.navigateByUrl('/manage-users');
38 | } else if (this.dataService.isBizAdmin() && this.dataService.loggedInUser.businessId) {
39 | this.router.navigateByUrl('/manage-users/users?bizId=' + this.dataService.loggedInUser.businessId);
40 | } else {
41 | this.infoDialog.alert("Unauthorized!", "Only a super admin or a business admin can login into user management");
42 | }
43 | }
44 | });
45 | }
46 |
47 | analytics() {
48 | this.loginService.performLogin(true, null, true, (done) => {
49 | if (!done) {
50 | this.infoDialog.alert("Login Required", "You must be logged in to your Ana chat server to view analytics");
51 | return;
52 | }
53 | if (this.dataService.loggedInUser) {
54 | if (this.dataService.isSuperAdmin()) {
55 | this.openAnalyticsPicker({ askFlowId: true });
56 | } else if ((this.dataService.isBizAdmin() || this.dataService.isFlowManager()) && this.dataService.loggedInUser.businessId) {
57 | this.openAnalyticsPicker({
58 | askFlowId: true,
59 | businessId: this.dataService.loggedInUser.businessId
60 | });
61 | } else {
62 | this.infoDialog.alert("Unauthorized!", "Only a super admin, a business admin or a flow manager can login into user management");
63 | }
64 | }
65 | });
66 | }
67 |
68 | deploy() {
69 | this.loginService.performLogin(true, null, true, (done) => {
70 | if (!done) {
71 | this.infoDialog.alert("Login Required", "You must be logged in to your Ana chat server to deploy your chatbot");
72 | return;
73 | }
74 | if (this.dataService.loggedInUser) {
75 | if (this.dataService.isSuperAdmin()) {
76 | this.openDeployPage({ askFlowId: true });
77 | } else if ((this.dataService.isBizAdmin() || this.dataService.isFlowManager()) && this.dataService.loggedInUser.businessId) {
78 | this.openDeployPage({
79 | askFlowId: true,
80 | businessId: this.dataService.loggedInUser.businessId
81 | });
82 | } else {
83 | this.infoDialog.alert("Unauthorized!", "Only a super admin, a business admin or a flow manager can login into user management");
84 | }
85 | }
86 | });
87 | }
88 |
89 | openAnalyticsPicker(params: BusinessPickerParam) {
90 | let d = this.dialog.open(BusinessPickerComponent, {
91 | width: '30%',
92 | data: params
93 | });
94 |
95 | d.afterClosed().subscribe((x: ChoosenBizAccChatProj) => {
96 | if (x && x.bizAccount && x.chatProj) {
97 | this.infoDialog.prompt("Analytics Server Url", "Please enter the analytics server url", (result) => {
98 | if (result) {
99 | localStorage.setItem('analyticsApiBase', result);
100 | let url = `/analytics?apiBase=${result}&businessId=${x.bizAccount.id}&businessName=${x.bizAccount.name}&chatFlowId=${x.chatProj.id}`;
101 | this.router.navigateByUrl(url);
102 | }
103 | }, localStorage.getItem('analyticsApiBase'));
104 | }
105 | });
106 | }
107 |
108 | openDeployPage(params: BusinessPickerParam) {
109 | let d = this.dialog.open(BusinessPickerComponent, {
110 | width: '30%',
111 | data: params
112 | });
113 |
114 | d.afterClosed().subscribe((x: ChoosenBizAccChatProj) => {
115 | if (x && x.bizAccount && x.chatProj) {
116 | let url = `/deploy?businessId=${x.bizAccount.id}&chatFlowId=${x.chatProj.id}`;
117 | this.router.navigateByUrl(url);
118 | }
119 | });
120 | }
121 | }
--------------------------------------------------------------------------------
/src/app/components/manage-users/biz-accounts/biz-accounts.component.css:
--------------------------------------------------------------------------------
1 | .ana-pagination {
2 | width: 100%;
3 | display: flex;
4 | flex-direction: row;
5 | }
6 |
7 | .ana-pagination .info {
8 | flex-grow: 1;
9 | line-height: 40px;
10 | }
11 |
12 | :host {
13 | width: 100%;
14 | }
15 |
16 | .app-content {
17 | padding-left: 15px;
18 | padding-right: 15px;
19 | width: 600px;
20 | margin-left: auto;
21 | margin-right: auto;
22 | }
23 |
24 | .list-item {
25 | background-color: white;
26 | }
27 |
28 | .list-item-text {
29 | width: 100%;
30 | }
31 |
32 | .list-item-buttons {
33 | display: flex;
34 | -ms-flex-direction: row;
35 | -webkit-flex-direction: row;
36 | flex-direction: row;
37 | justify-content: space-between;
38 | }
39 |
40 | .app-content-header {
41 | display: flex;
42 | flex-direction: row;
43 | margin: 11px 0;
44 | }
45 |
46 | .app-content-header-text {
47 | flex-grow: 1;
48 | margin-top: 14px;
49 | }
50 |
51 | .list-item-hint {
52 | font-size: 13px;
53 | opacity: 0.5;
54 | font-style: italic
55 | }
56 |
57 | mat-list-item {
58 | margin-bottom: 10px;
59 | }
60 |
61 | .search-text {
62 | margin-right: 10px;
63 | }
64 |
--------------------------------------------------------------------------------
/src/app/components/manage-users/biz-accounts/biz-accounts.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
17 |
18 | Looks like there are no business accounts yet.
19 |
20 |
21 |
34 |
0">
35 |
36 |
37 | {{acc.name}} {{BusinessAccountStatus[acc.status].toLowerCase()}}
38 |
39 |
40 |
43 |
46 |
49 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/src/app/components/manage-users/biz-accounts/biz-accounts.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, AfterViewInit, ViewChild, Injectable } from '@angular/core';
2 | import { Router, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from "@angular/router";
3 | import { DataService } from '../../../services/data.service';
4 | import { InfoDialogService } from '../../../services/info-dialog.service';
5 | import { MatDialog } from '@angular/material';
6 | import { LoginComponent } from '../../shared/login/login.component';
7 | import { AppHeaderBarComponent } from '../../shared/app-header-bar/app-header-bar.component';
8 | import { EditBusinessAccountComponent } from '../../shared/edit-business-account/edit-business-account.component';
9 | import { BusinessAccount, BusinessAccountStatus } from '../../../models/data.models';
10 |
11 | @Component({
12 | selector: 'app-biz-accounts',
13 | templateUrl: './biz-accounts.component.html',
14 | styleUrls: ['./biz-accounts.component.css']
15 | })
16 | export class BizAccountsComponent implements AfterViewInit {
17 |
18 | @ViewChild('appHeader')
19 | appHeader: AppHeaderBarComponent;
20 |
21 | ngAfterViewInit(): void {
22 | this.appHeader.afterInit = () => {
23 | this.loadAccounts();
24 | };
25 | }
26 |
27 | constructor(
28 | private dataService: DataService,
29 | private dialog: MatDialog,
30 | private router: Router,
31 | private infoDialog: InfoDialogService) { }
32 |
33 | accounts: BusinessAccount[];
34 | page: number = 0;
35 | totalPages: number = 0;
36 |
37 | prevPage() {
38 | if (this.page > 0) {
39 | this.page--;
40 | this.loadAccounts();
41 | }
42 | }
43 |
44 | nextPage() {
45 | if (this.page < this.totalPages) {
46 | this.page++;
47 | this.loadAccounts();
48 | }
49 | }
50 | search: string = "";
51 |
52 | searchStart() {
53 | this.page = 0;
54 | this.loadAccounts();
55 | }
56 |
57 | loadAccounts() {
58 | this.infoDialog.showSpinner();
59 |
60 | this.dataService.getBusinessAccounts(this.search, this.page).subscribe(x => {
61 | this.infoDialog.hideSpinner();
62 | if (x.success) {
63 | this.accounts = x.data.content;
64 | this.totalPages = x.data.totalPages;
65 | } else
66 | this.dataService.handleTypedError(x.error, "Unable to load business accounts", "Something went wrong while loading business accounts. Please try again.");
67 | }, err => {
68 | this.infoDialog.hideSpinner();
69 | this.dataService.handleError(err, "Unable to load business accounts", "Something went wrong while loading business accounts. Please try again.");
70 | });
71 | }
72 |
73 | manageUsers(account: BusinessAccount) {
74 | this.router.navigateByUrl(`/manage-users/users?bizId=${account.id}`);
75 | }
76 |
77 | editBusinessAccount(data?: BusinessAccount) {
78 | let d = this.dialog.open(EditBusinessAccountComponent, {
79 | width: '40%',
80 | data: data
81 | });
82 | d.afterClosed().subscribe(x => {
83 | if (x == true) {
84 | this.loadAccounts();
85 | }
86 | });
87 | }
88 |
89 | BusinessAccountStatus = BusinessAccountStatus;
90 | updateBusinessAccountStatus(account: BusinessAccount, status: BusinessAccountStatus) {
91 | let work = (status == BusinessAccountStatus.ACTIVE ? "activate" : "deactivate");
92 | this.infoDialog.confirm("Confirmation", `Are you sure you want to ${work} the business account?`, (ok) => {
93 | if (ok) {
94 | this.infoDialog.showSpinner();
95 | this.dataService.updateBusinessAccountStatus(account, status).subscribe(x => {
96 | this.infoDialog.hideSpinner();
97 | if (x.success) {
98 | this.infoDialog.alert("Done", "Business account status updated");
99 | this.loadAccounts();
100 | } else {
101 | this.dataService.handleTypedError(x.error, "Unable to update business account status", "Something went wrong while updating the business account. Please try again.");
102 | }
103 | }, err => {
104 | this.infoDialog.hideSpinner();
105 | this.dataService.handleError(err, "Unable to update business account status", "Something went wrong while updating the business account. Please try again.");
106 | });
107 | }
108 | });
109 | }
110 | }
111 |
112 | @Injectable()
113 | export class CanActivateBizAccountComponent implements CanActivate {
114 | constructor(
115 | private dataService: DataService) { }
116 |
117 | canActivate(
118 | route: ActivatedRouteSnapshot,
119 | state: RouterStateSnapshot) {
120 | return this.dataService.isSuperAdmin();
121 | }
122 | }
--------------------------------------------------------------------------------
/src/app/components/manage-users/manage-users.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule, Injectable } from '@angular/core';
2 | import { RouterModule, Routes, CanActivate, ActivatedRouteSnapshot, RouterStateSnapshot } from '@angular/router';
3 | import { BizAccountsComponent, CanActivateBizAccountComponent } from './biz-accounts/biz-accounts.component';
4 | import { SharedModule } from '../../shared.module';
5 | import { UsersComponent } from './users/users.component';
6 | import { DataService } from '../../services/data.service';
7 | export const MANAGE_USERS_ROUTES: Routes = [
8 | {
9 | path: "",
10 | component: BizAccountsComponent,
11 | canActivate: [CanActivateBizAccountComponent]
12 | },
13 | {
14 | path: "users",
15 | component: UsersComponent
16 | }
17 | ];
18 |
19 | @NgModule({
20 | declarations: [
21 | BizAccountsComponent,
22 | UsersComponent
23 | ],
24 | imports: [
25 | SharedModule
26 | ],
27 | providers: [
28 | CanActivateBizAccountComponent
29 | ]
30 | })
31 | export class ManageUsersModule { }
--------------------------------------------------------------------------------
/src/app/components/manage-users/users/users.component.css:
--------------------------------------------------------------------------------
1 | .ana-pagination {
2 | width: 100%;
3 | display: flex;
4 | flex-direction: row;
5 | }
6 |
7 | .ana-pagination .info {
8 | flex-grow: 1;
9 | line-height: 40px;
10 | }
11 |
12 | :host {
13 | width: 100%;
14 | }
15 |
16 | .app-content {
17 | padding-left: 15px;
18 | padding-right: 15px;
19 | width: 600px;
20 | margin-left: auto;
21 | margin-right: auto;
22 | }
23 |
24 | .list-item {
25 | background-color: white;
26 | }
27 |
28 | .list-item-text {
29 | width: 100%;
30 | }
31 |
32 | .app-content-header {
33 | display: flex;
34 | flex-direction: row;
35 | margin: 11px 0;
36 | }
37 |
38 | .app-content-header-text {
39 | flex-grow: 1;
40 | margin-top: 14px;
41 | }
42 |
43 | .list-item-hint {
44 | font-size: 13px;
45 | opacity: 0.5;
46 | font-style: italic
47 | }
48 |
49 | mat-list-item {
50 | margin-bottom: 10px;
51 | }
52 | .search-text {
53 | margin-right: 10px;
54 | }
--------------------------------------------------------------------------------
/src/app/components/manage-users/users/users.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
17 |
18 | Looks like there are no users yet.
19 |
20 |
21 |
34 |
35 |
0">
36 |
37 |
38 | {{user.name || user.userName }} {{userRole(user)}}
39 |
40 |
43 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/src/app/components/manage-users/users/users.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, ViewChild, AfterViewInit } from '@angular/core';
2 | import { Route, ActivatedRoute, Router } from '@angular/router';
3 | import { User, BusinessAccount } from '../../../models/data.models';
4 | import { DataService } from '../../../services/data.service';
5 | import { InfoDialogService } from '../../../services/info-dialog.service';
6 | import { MatDialog } from '@angular/material';
7 | import { CreateUserComponent, UserDialogParam, UserDialogMode } from '../../shared/create-user/create-user.component';
8 | import { AppHeaderBarComponent } from '../../shared/app-header-bar/app-header-bar.component';
9 | import { UpdatePasswordComponent } from '../../shared/update-password/update-password.component';
10 |
11 | @Component({
12 | selector: 'app-users',
13 | templateUrl: './users.component.html',
14 | styleUrls: ['./users.component.css']
15 | })
16 | export class UsersComponent implements OnInit, AfterViewInit {
17 | ngAfterViewInit(): void {
18 | this.appHeader.afterInit = () => {
19 | this.route.queryParamMap.subscribe(x => {
20 | let bizId = x.get('bizId');
21 | if (bizId) {
22 | this.bizId = bizId;
23 | this.loadUsers();
24 | this.loadBusinessDetails();
25 | }
26 | });
27 | };
28 | this.appHeader.goBack = () => {
29 | if (this.dataService.isSuperAdmin()) {
30 | this.router.navigateByUrl('/manage-users');
31 | } else {
32 | this.router.navigateByUrl('/');
33 | }
34 | };
35 | }
36 |
37 | @ViewChild(AppHeaderBarComponent)
38 | appHeader: AppHeaderBarComponent;
39 |
40 | businessAccount: BusinessAccount;
41 | bizId: string;
42 | constructor(
43 | private route: ActivatedRoute,
44 | private router: Router,
45 | private infoDialog: InfoDialogService,
46 | private dialog: MatDialog,
47 | private dataService: DataService) {
48 | }
49 |
50 | ngOnInit() {
51 | }
52 |
53 | search: string = "";
54 | searchStart() {
55 | this.page = 0;
56 | this.loadUsers();
57 | }
58 |
59 | loadBusinessDetails() {
60 | this.infoDialog.showSpinner();
61 |
62 | this.dataService.getBusinessDetails(this.bizId).subscribe(x => {
63 | this.infoDialog.hideSpinner();
64 | this.businessAccount = x.data;
65 | }, err => {
66 | this.infoDialog.hideSpinner();
67 | this.dataService.handleError(err, "Unable to load business details", "Something went wrong while trying to load business account details. Please try again.")
68 | });
69 | }
70 |
71 | createUserDialog() {
72 | let d = this.dialog.open(CreateUserComponent, {
73 | width: '60%',
74 | data: {
75 | bizId: this.bizId,
76 | mode: UserDialogMode.Create,
77 | }
78 | });
79 | d.afterClosed().subscribe(x => {
80 | if (x == true)
81 | this.loadUsers();
82 | });
83 | }
84 |
85 | users: User[] = [];
86 | page: number = 0;
87 | totalPages: number = 0;
88 |
89 | prevPage() {
90 | if (this.page > 0) {
91 | this.page--;
92 | this.loadUsers();
93 | }
94 | }
95 | nextPage() {
96 | if (this.page < this.totalPages) {
97 | this.page++;
98 | this.loadUsers();
99 | }
100 | }
101 | view(user: User) {
102 | this.dialog.open(CreateUserComponent, {
103 | width: '60%',
104 | data: {
105 | mode: UserDialogMode.View,
106 | user: user
107 | }
108 | })
109 | }
110 | loadUsers() {
111 | if (this.bizId) {
112 | this.infoDialog.showSpinner();
113 | this.dataService.getUsers(this.bizId, this.search, this.page).subscribe(x => {
114 | this.infoDialog.hideSpinner();
115 | //if (x.success) {
116 | this.users = x.content;//.filter(x => x.roles && x.roles.length > 0);
117 | this.totalPages = x.totalPages;
118 | //} else {
119 | // debugger;
120 | // this.dataService.handleTypedError(x.error, "Unable to load users", "Something went wrong while loading the users. Please try again.");
121 | //}
122 | }, err => {
123 | this.infoDialog.hideSpinner();
124 | this.dataService.handleError(err, "Unable to load users", "Something went wrong while loading the users. Please try again.");
125 | });
126 | }
127 | }
128 |
129 | updateUserPassword(user: User) {
130 | this.dialog.open(UpdatePasswordComponent, {
131 | width: '40%',
132 | data: user
133 | });
134 | }
135 |
136 | userRole(user: User) {
137 | if (user.roles) {
138 | return user.roles.map(x => x.label).join(', ');
139 | }
140 | return "";
141 | }
142 | }
143 |
--------------------------------------------------------------------------------
/src/app/components/shared/ana-cloud-signup/ana-cloud-signup.component.css:
--------------------------------------------------------------------------------
1 | form {
2 | display: flex;
3 | flex-direction: column;
4 | }
5 |
--------------------------------------------------------------------------------
/src/app/components/shared/ana-cloud-signup/ana-cloud-signup.component.html:
--------------------------------------------------------------------------------
1 | Create your Ana Cloud account
2 |
3 |
37 |
38 |
39 | {{errorMessage}}
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/src/app/components/shared/ana-cloud-signup/ana-cloud-signup.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { RegisterOnAnaCloudDetails } from '../../../models/data.models';
3 | import { GlobalsService } from '../../../services/globals.service';
4 | import { NgForm } from '@angular/forms';
5 | import { DataService } from '../../../services/data.service';
6 | import { InfoDialogService } from '../../../services/info-dialog.service';
7 |
8 | @Component({
9 | selector: 'app-ana-cloud-signup',
10 | templateUrl: './ana-cloud-signup.component.html',
11 | styleUrls: ['./ana-cloud-signup.component.css']
12 | })
13 | export class AnaCloudSignupComponent implements OnInit {
14 |
15 | constructor(
16 | private global: GlobalsService,
17 | private data: DataService,
18 | private infoDialog: InfoDialogService
19 | ) { }
20 | details: PublicRegistrationDetails = {
21 | companyName: "",
22 | confirmPassword: "",
23 | displayName: "",
24 | email: "",
25 | password: "",
26 | phone: ""
27 | };
28 |
29 | errorMessage: string;
30 |
31 | allValid(): boolean {
32 | let d = this.details;
33 | if (!d || !d.companyName || !d.displayName) {
34 | this.errorMessage = "Please fill all the mandatory fields.";
35 | return false;
36 | }
37 |
38 | if (!d.email || !this.global.emailValid(d.email)) {
39 | this.errorMessage = "Please enter a valid email address. Verification email will be sent to it.";
40 | return false;
41 | }
42 | if (!d.phone || !this.global.phoneValid(d.phone)) {
43 | this.errorMessage = "Please enter a valid phone number.";
44 | return false;
45 | }
46 | if (!d.password || d.password.length < 6) {
47 | this.errorMessage = "Minimum length of the password is 6 characters.";
48 | return false;
49 | }
50 | if (d.password != d.confirmPassword) {
51 | this.errorMessage = "Password and confirm password do not match.";
52 | return false;
53 | }
54 | this.errorMessage = null;
55 | return true;
56 | }
57 |
58 | ngOnInit() {
59 | }
60 |
61 | create() {
62 | if (this.allValid()) {
63 | this.infoDialog.showSpinner();
64 | this.data.getRoles().subscribe(x => {
65 | let roles = x.data.filter(x => ["BUSINESS_ADMIN"].indexOf(x.role) != -1);
66 | if (roles && roles.length > 0) {
67 | let roleId = roles[0].id;
68 | this.data.registerOnAnaCloud({
69 | business: {
70 | email: this.details.email,
71 | phone: this.details.phone,
72 | name: this.details.companyName + " - " + this.details.displayName,
73 | },
74 | user: {
75 | email: this.details.email,
76 | phone: this.details.phone,
77 | name: this.details.displayName,
78 | password: this.details.password,
79 | roleIds: [roleId]
80 | }
81 | }).subscribe(x => {
82 | this.infoDialog.hideSpinner();
83 | if (x.data) {
84 |
85 | } else {
86 | this.data.handleTypedError(x.error, "Unable to register", "Something went wrong while trying to register. Please try again.");
87 | }
88 | }, err => {
89 | this.infoDialog.hideSpinner();
90 | this.data.handleError(err, "Unable to register", "Something went wrong while trying to register. Please try again.");
91 | });
92 | }
93 | }, err => {
94 | this.infoDialog.hideSpinner();
95 | this.data.handleError(err, "Unable to register", "Something went wrong while trying to register. Please try again.");
96 | });
97 | } else {
98 | this.infoDialog.alert("Invalid Details", this.errorMessage);
99 | }
100 | }
101 | }
102 |
103 | export interface PublicRegistrationDetails {
104 | displayName: string;
105 | phone: string;
106 | email: string;
107 | password: string;
108 | confirmPassword: string;
109 | companyName: string;
110 | }
--------------------------------------------------------------------------------
/src/app/components/shared/app-header-bar/app-header-bar.component.css:
--------------------------------------------------------------------------------
1 | app-header-bar {
2 | width: 100%;
3 | }
4 |
5 | app-header-bar > .user-info {
6 | float: right;
7 | }
8 |
9 | .back-button {
10 | margin-right: 10px;
11 | }
12 |
13 | .toolbar-icon {
14 | padding: 0 14px;
15 | }
16 |
17 | .toolbar-spacer {
18 | flex: 1 1 auto;
19 | }
20 |
21 | .user-info {
22 | display: flex;
23 | flex-direction: column;
24 | }
25 |
26 | .user-role {
27 | font-size: 13px;
28 | opacity: .7;
29 | margin-top: -10px;
30 | align-self: flex-end;
31 | }
32 |
33 |
--------------------------------------------------------------------------------
/src/app/components/shared/app-header-bar/app-header-bar.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 | {{title}}
7 |
8 |
9 |
10 | {{loggedInUser.name | titlecase }}
11 |
12 |
13 | {{roles()}}
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
24 |
25 |
29 |
33 |
34 |
35 |
--------------------------------------------------------------------------------
/src/app/components/shared/app-header-bar/app-header-bar.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, AfterViewInit, Input } from '@angular/core';
2 | import { Router } from "@angular/router";
3 | import { DataService } from '../../../services/data.service';
4 | import { InfoDialogService } from '../../../services/info-dialog.service';
5 | import { MatDialog } from '@angular/material';
6 | import { LoginComponent } from '../../shared/login/login.component';
7 | import { LoginData } from '../../../models/data.models';
8 | import { LoginService } from '../../../services/login.service';
9 | import { ChangePasswordComponent } from '../change-password/change-password.component';
10 |
11 | @Component({
12 | selector: 'app-header-bar',
13 | templateUrl: './app-header-bar.component.html',
14 | styleUrls: ['./app-header-bar.component.css']
15 | })
16 | export class AppHeaderBarComponent implements OnInit, AfterViewInit {
17 |
18 | @Input('goBack')
19 | goBack = () => {
20 | this.router.navigateByUrl('/');
21 | };
22 |
23 | @Input('logoutNavigation')
24 | logoutNavigation: string = '/';
25 |
26 | @Input('skipAuth')
27 | skipAuth: boolean = false;
28 |
29 | @Input('hideBackButton')
30 | hideBackButton: boolean = false;
31 |
32 | @Input('title')
33 | title: string = "";
34 |
35 | constructor(
36 | private dataService: DataService,
37 | private dialog: MatDialog,
38 | private router: Router,
39 | private infoDialog: InfoDialogService,
40 | private loginService: LoginService) { }
41 |
42 | ngOnInit() {
43 |
44 | }
45 |
46 | ngAfterViewInit(): void {
47 | this.checkAndUpdate();
48 | }
49 |
50 | checkAndUpdate() {
51 | Promise.resolve(true).then(() => {
52 | this.loginService.performLogin(this.skipAuth, "/", true, (done) => {
53 | if (done) {
54 | this.loggedInUser = this.dataService.loggedInUser;
55 | if (this.afterInit)
56 | this.afterInit();
57 | }
58 | });
59 | });
60 | }
61 |
62 | loginDialog() {
63 | this.loginService.performLogin(false, null, true, (done) => {
64 | if (this.dataService.loggedInUser) {
65 | this.loggedInUser = this.dataService.loggedInUser;
66 | }
67 | });
68 | }
69 |
70 | logout() {
71 | this.dataService.logout();
72 | this.loggedInUser = null;
73 | if (this.logoutNavigation) {
74 | this.router.navigateByUrl(this.logoutNavigation);
75 | }
76 | }
77 |
78 | changePassword() {
79 | this.dialog.open(ChangePasswordComponent, {
80 | width: '60%',
81 | });
82 | }
83 |
84 | loggedInUser: LoginData;
85 |
86 | roles() {
87 | if (this.loggedInUser && this.loggedInUser.roles)
88 | return this.loggedInUser.roles.map(x => x.label).join(', ');
89 | return "";
90 | }
91 |
92 | afterInit: () => void;
93 | }
94 |
--------------------------------------------------------------------------------
/src/app/components/shared/business-picker/business-picker.component.css:
--------------------------------------------------------------------------------
1 | .full-width {
2 | width: 100%;
3 | }
4 |
5 | .desc {
6 | font-size: 11px;
7 | opacity: 0.6;
8 | }
--------------------------------------------------------------------------------
/src/app/components/shared/business-picker/business-picker.component.html:
--------------------------------------------------------------------------------
1 | {{ title }}
2 |
3 |
4 |
5 |
6 |
7 | {{ option.name }} Id: {{ option.id }}
8 |
9 |
10 |
11 | 0">
12 |
13 |
14 |
15 | {{ option.name }} Id: {{ option.id }}
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/app/components/shared/change-password/change-password.component.css:
--------------------------------------------------------------------------------
1 | .form {
2 | display: flex;
3 | -ms-flex-direction: column;
4 | -webkit-flex-direction: column;
5 | flex-direction: column;
6 | }
7 |
8 | mat-dialog-actions button {
9 | margin-left: 0 !important;
10 | margin-right: 8px !important;
11 | }
12 |
--------------------------------------------------------------------------------
/src/app/components/shared/change-password/change-password.component.html:
--------------------------------------------------------------------------------
1 | Change Password
2 |
3 |
4 |
19 |
20 |
21 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/src/app/components/shared/change-password/change-password.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, Optional, Inject } from '@angular/core';
2 | import { GlobalsService } from '../../../services/globals.service';
3 | import { InfoDialogService } from '../../../services/info-dialog.service';
4 | import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material';
5 | import { EditBusinessAccountComponent } from '../edit-business-account/edit-business-account.component';
6 | import { DataService } from '../../../services/data.service';
7 | import { User } from '../../../models/data.models';
8 |
9 | @Component({
10 | selector: 'app-change-password',
11 | templateUrl: './change-password.component.html',
12 | styleUrls: ['./change-password.component.css']
13 | })
14 | export class ChangePasswordComponent implements OnInit {
15 |
16 | constructor(
17 | private global: GlobalsService,
18 | private infoDialog: InfoDialogService,
19 | private dataService: DataService,
20 | private dialogRef: MatDialogRef) {
21 |
22 | }
23 |
24 | password: string;
25 |
26 | newPassword: string;
27 | confirmPassword: string;
28 |
29 | ngOnInit() {
30 | }
31 |
32 | save() {
33 | if (!this.global.pwdMatch(this.newPassword, this.confirmPassword)) {
34 | this.infoDialog.alert("Passwords do not match or not secure", "Please ensure the password and confirm password is same. Also, a password must be at least 6 characters.");
35 | return;
36 | }
37 | this.infoDialog.showSpinner();
38 |
39 | this.dataService.changeCurrentUserPassword(this.password, this.newPassword).subscribe(x => {
40 | this.infoDialog.hideSpinner();
41 | if (x.success) {
42 | this.infoDialog.alert("Password changed", "Password has been changed successfully", () => {
43 | this.dialogRef.close();
44 | });
45 | } else {
46 | this.dataService.handleTypedError(x.error, "Unable to change password", "Something went wrong while trying to change the password.")
47 | }
48 | }, err => {
49 | this.infoDialog.hideSpinner();
50 | this.dataService.handleError(err, "Unable to change password", "Something went wrong while trying to change the password.")
51 | });
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/src/app/components/shared/chat-server-manager/chat-server-manager.component.css:
--------------------------------------------------------------------------------
1 | mat-form-field, mat-select {
2 | width: 100%;
3 | }
4 |
5 | .bottom-actions > button {
6 | margin: 20px 10px;
7 | }
8 |
9 | mat-action-row > button {
10 | margin: 10px;
11 | }
12 |
13 | /*mat-action-row {
14 | margin: 0 -10px;
15 | display: block;
16 | }*/
17 | .bottom-actions {
18 | display: block;
19 | text-align: center;
20 | }
21 |
22 | /*.mat-dialog-actions {
23 | margin-left: -8px;
24 | margin-right: -8px;
25 | }*/
26 |
--------------------------------------------------------------------------------
/src/app/components/shared/chat-server-manager/chat-server-manager.component.html:
--------------------------------------------------------------------------------
1 | Ana chat server connections
2 |
3 |
4 |
5 | Looks like you don't have any saved Ana chat server connections.
Start by adding one.
6 |
7 |
8 |
9 |
10 |
11 |
12 | insert_link
13 | {{connectionAlias(conn)}}
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/src/app/components/shared/chat-server-manager/chat-server-manager.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { Router } from '@angular/router';
3 | import { MatDialogRef } from '@angular/material';
4 | import { ChatServerConnection, ChatBotProject } from '../../../models/app.models';
5 | import { SettingsService } from '../../../services/settings.service';
6 | import { ChatFlowService } from '../../../services/chatflow.service';
7 | import { InfoDialogService } from '../../../services/info-dialog.service';
8 | import { MatSnackBar, MatDialog } from '@angular/material';
9 | import { GetAnaChatServerComponent } from '../get-ana-chat-server/get-ana-chat-server.component';
10 |
11 | @Component({
12 | selector: 'app-chat-server-manager',
13 | templateUrl: './chat-server-manager.component.html',
14 | styleUrls: ['./chat-server-manager.component.css'],
15 | })
16 | export class ChatServerManagerComponent implements OnInit {
17 | constructor(
18 | public settings: SettingsService,
19 | public chatFlowService: ChatFlowService,
20 | public snakbar: MatSnackBar,
21 | public infoDialog: InfoDialogService,
22 | public dialog: MatDialog,
23 | public router: Router,
24 | public dialogRef: MatDialogRef) {
25 |
26 | this.savedConnections = this.settings.loadSavedConnections();
27 | }
28 |
29 | savedConnections: ChatServerConnection[] = [];
30 |
31 | ngOnInit(): void {
32 | }
33 |
34 | connectionAlias(conn: ChatServerConnection) {
35 | return conn.Name || conn.ServerUrl || 'New Ana chat server';
36 | }
37 |
38 | deleteConnection(conn: ChatServerConnection) {
39 | var current = this.savedConnections.indexOf(conn);
40 | if (current != -1) {
41 |
42 | this.infoDialog.confirm("Sure?", `Delete chat server connection '${this.connectionAlias(conn)}'`, (ok) => {
43 | if (ok) {
44 | this.savedConnections.splice(current, 1);
45 | this.saveConnections(false);
46 | }
47 | });
48 | }
49 | }
50 |
51 | addChatProjectToConn(conn: ChatServerConnection) {
52 | if (!conn.ChatProjects)
53 | conn.ChatProjects = [];
54 | conn.ChatProjects.push({
55 | CreatedOn: new Date(),
56 | Id: 'new-chat-project',
57 | Name: 'New Chat Project',
58 | UpdatedOn: new Date()
59 | });
60 | }
61 |
62 | deleteProject(conn: ChatServerConnection, proj: ChatBotProject) {
63 | var current = conn.ChatProjects.indexOf(proj);
64 | if (current != -1) {
65 | this.infoDialog.confirm("Sure?", `Delete chat project '${proj.Name}'`, (ok) => {
66 | if (ok) {
67 | conn.ChatProjects.splice(current, 1);
68 | this.saveConnections(false);
69 | }
70 | });
71 | }
72 | }
73 |
74 | saveConnections(close: boolean) {
75 | if (this.savedConnections && this.savedConnections.length > 0) {
76 | let invalidPublishServers = this.savedConnections.filter(x => !x.ServerUrl || !x.Name);
77 | if (invalidPublishServers.length > 0) {
78 | this.infoDialog.alert('Incomplete Details', `One or more of your Ana chat servers have Server Url or Name. Please fill them and try again.`);
79 | return;
80 | }
81 |
82 | try {
83 | let emptyChatProjects = this.savedConnections.filter(x => !x.ChatProjects || x.ChatProjects.length <= 0);
84 | if (emptyChatProjects.length != this.savedConnections.length) {
85 | let invalidChatProjects = this.savedConnections.filter(x => x.ChatProjects && x.ChatProjects.length > 0).map(x => x.ChatProjects).reduce((a, b) => a.concat(b)).filter(x => !x.Id || !x.Name);
86 | if ((emptyChatProjects.length > 0 && invalidChatProjects.length > 0)) {
87 | this.infoDialog.alert('Incomplete Details', `One or more of the chat projects in your chat server connections is incomplete. Please fill them and try again.`);
88 | return;
89 | }
90 | }
91 | } catch (e) {
92 | console.log(e);
93 | this.infoDialog.alert('Incomplete Details', `One or more of the chat projects in your chat server connections is incomplete. Please fill them and try again.`);
94 | return;
95 | }
96 | }
97 |
98 | this.settings.saveSavedConnections(this.savedConnections);
99 |
100 | this.snakbar.open('Ana chat servers saved!', 'Dismiss', {
101 | duration: 3000
102 | });
103 |
104 | if (close) {
105 | this.dialogRef.close();
106 | }
107 | }
108 |
109 | addConnection() {
110 | let newConn: ChatServerConnection = {
111 | APIKey: '',
112 | APISecret: '',
113 | IsDefault: false,
114 | Name: '',
115 | ServerUrl: '',
116 | ChatProjects: []
117 | };
118 | this.savedConnections.push(newConn);
119 | }
120 |
121 | getAnaChatServer() {
122 | this.dialog.open(GetAnaChatServerComponent, {
123 | width: 'auto',
124 | disableClose: true
125 | });
126 | }
127 | }
--------------------------------------------------------------------------------
/src/app/components/shared/create-chatbot/create-chatbot.component.css:
--------------------------------------------------------------------------------
1 | .form {
2 | display: flex;
3 | -ms-flex-direction: column;
4 | -webkit-flex-direction: column;
5 | flex-direction: column;
6 | }
7 |
--------------------------------------------------------------------------------
/src/app/components/shared/create-chatbot/create-chatbot.component.html:
--------------------------------------------------------------------------------
1 | Add new chatbot project
2 |
3 |
4 |
15 |
16 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/src/app/components/shared/create-chatbot/create-chatbot.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, Inject } from '@angular/core';
2 | import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material';
3 | import { ChatBotReferance, ChatServerConnection } from '../../../models/app.models';
4 | import { SettingsService } from '../../../services/settings.service';
5 | import { ChatFlowService } from '../../../services/chatflow.service';
6 | import { InfoDialogService } from '../../../services/info-dialog.service';
7 | import { ChatServerManagerComponent } from '../chat-server-manager/chat-server-manager.component';
8 | import * as models from '../../../models/chatflow.models';
9 | import { DataService } from '../../../services/data.service';
10 | import { LoginService } from '../../../services/login.service';
11 | import { ChatProject } from '../../../models/data.models';
12 | import { GlobalsService } from '../../../services/globals.service';
13 |
14 | @Component({
15 | selector: 'app-create-chatbot',
16 | templateUrl: './create-chatbot.component.html',
17 | styleUrls: ['./create-chatbot.component.css']
18 | })
19 | export class CreateChatbotComponent implements OnInit {
20 |
21 | constructor(private settings: SettingsService,
22 | private globals: GlobalsService,
23 | private dataService: DataService,
24 | private loginService: LoginService,
25 | private dialog: MatDialog,
26 | private infoDialog: InfoDialogService,
27 | private dialogRef: MatDialogRef,
28 | @Inject(MAT_DIALOG_DATA) private bizDetails: BusinessDetails) {
29 | if (bizDetails) {
30 | this.chatProject.businessId = bizDetails.id;
31 | //this.chatProject.businessName = bizDetails.name;
32 | }
33 | }
34 |
35 | ngOnInit() {
36 | }
37 |
38 | chatProject: ChatProject = {
39 | businessId: "",
40 | //businessName: "",
41 | id: "",
42 | name: ""
43 | };
44 |
45 | create() {
46 | this.infoDialog.showSpinner();
47 | this.dataService.createChatProject(this.chatProject).subscribe(x => {
48 | this.infoDialog.hideSpinner();
49 | if (x.success) {
50 | this.dialogRef.close(this.chatProject);
51 | } else {
52 | this.dataService.handleTypedError(x.error, "Unable to create chatbot project", "Something went wrong while creating the chatbot project. Please try again.");
53 | this.dialogRef.close();
54 | }
55 | }, err => {
56 | this.infoDialog.hideSpinner();
57 | this.dataService.handleError(err, "Unable to create chatbot project", "Something went wrong while creating the chatbot project. Please try again.");
58 | this.dialogRef.close();
59 | });
60 | }
61 | }
62 |
63 | export interface BusinessDetails {
64 | id: string;
65 | name?: string;
66 | }
67 |
--------------------------------------------------------------------------------
/src/app/components/shared/create-user/create-user.component.css:
--------------------------------------------------------------------------------
1 | .form {
2 | display: flex;
3 | -ms-flex-direction: column;
4 | -webkit-flex-direction: column;
5 | flex-direction: column;
6 | }
7 |
8 | mat-dialog-actions button {
9 | margin-left: 0 !important;
10 | margin-right: 8px !important;
11 | }
12 |
13 | .mat-input-element:disabled {
14 | color: black;
15 | }
16 |
--------------------------------------------------------------------------------
/src/app/components/shared/create-user/create-user.component.html:
--------------------------------------------------------------------------------
1 | {{title}}
2 |
3 |
4 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/src/app/components/shared/create-user/create-user.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, Optional, Inject } from '@angular/core';
2 | import { UserRegisterModel, Role, User } from '../../../models/data.models';
3 | import { GlobalsService } from '../../../services/globals.service';
4 | import { InfoDialogService } from '../../../services/info-dialog.service';
5 | import { DataService } from '../../../services/data.service';
6 | import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material';
7 | import { EditBusinessAccountComponent } from '../edit-business-account/edit-business-account.component';
8 |
9 | @Component({
10 | selector: 'app-create-user',
11 | templateUrl: './create-user.component.html',
12 | styleUrls: ['./create-user.component.css']
13 | })
14 | export class CreateUserComponent implements OnInit {
15 | title: string = "";
16 | constructor(
17 | private global: GlobalsService,
18 | private infoDialog: InfoDialogService,
19 | private dataService: DataService,
20 | private dialogRef: MatDialogRef,
21 | @Optional()
22 | @Inject(MAT_DIALOG_DATA)
23 | public param: UserDialogParam) {
24 | if (param.mode == UserDialogMode.Create) {
25 | this.title = "Create User";
26 | this.user = {
27 | businessId: param.bizId,
28 | email: "",
29 | name: "",
30 | phone: "",
31 | password: "",
32 | roleIds: []
33 | };
34 | } else if (param.mode == UserDialogMode.View) {
35 | this.title = "User Details";
36 |
37 | this.user = {
38 | businessId: param.bizId,
39 | email: param.user.email || param.user.userName,
40 | name: param.user.name || param.user.userName,
41 | phone: param.user.phone,
42 | password: "",
43 | roleIds: []
44 | };
45 | }
46 | this.loadRoles();
47 | }
48 | UserDialogMode = UserDialogMode;
49 | ngOnInit() {
50 | }
51 | confirmPassword: string;
52 | selectedRole: Role;
53 |
54 | userRoleDisplay() {
55 | if (this.param.user && this.param.user.roles) {
56 | return this.param.user.roles.map(x => x.label).join(', ');
57 | }
58 | return "";
59 | }
60 |
61 | user: UserRegisterModel;
62 | roles: Role[];
63 |
64 | loadRoles() {
65 | this.dataService.getRoles().subscribe(x => {
66 | this.roles = x.data.filter(x => ["SUPER_ADMIN", "END_USER"].indexOf(x.role) == -1);
67 | });
68 | }
69 |
70 | save() {
71 | if (!this.global.emailValid(this.user.email)) {
72 | this.infoDialog.alert("Invalid Email", "Please enter a valid email address");
73 | return;
74 | }
75 | if (!this.global.phoneValid(this.user.phone)) {
76 | this.infoDialog.alert("Invalid Phone Number", "Please enter a valid phone number");
77 | return;
78 | }
79 | if (!this.global.pwdMatch(this.user.password, this.confirmPassword)) {
80 | this.infoDialog.alert("Passwords do not match or not secure", "Please ensure the password and confirm password is same. Also, a password must be at least 6 characters.");
81 | return;
82 | }
83 | if (!this.selectedRole) {
84 | this.infoDialog.alert("Role not selected", "Please select a role for the user");
85 | return;
86 | }
87 | this.user.roleIds = [this.selectedRole.id];
88 | this.infoDialog.showSpinner();
89 | debugger;
90 | this.dataService.createUser(this.user).subscribe(x => {
91 | this.infoDialog.hideSpinner();
92 | if (x.success) {
93 | this.infoDialog.alert("User created", "The user has been created successfully", () => {
94 | this.dialogRef.close(true);
95 | });
96 | } else {
97 | this.dataService.handleTypedError(x.error, "Unable to create the user", "Something went wrong while trying to create the user. Please try again in some time.");
98 | }
99 | }, err => {
100 | this.infoDialog.hideSpinner();
101 | this.dataService.handleError(err, "Unable to create the user", "Something went wrong while trying to create the user. Please try again in some time.");
102 | });
103 | }
104 | }
105 |
106 | export enum UserDialogMode {
107 | View,
108 | Create
109 | }
110 |
111 | export interface UserDialogParam {
112 | mode: UserDialogMode,
113 | bizId?: string;
114 | user: User;
115 | }
--------------------------------------------------------------------------------
/src/app/components/shared/edit-business-account/edit-business-account.component.css:
--------------------------------------------------------------------------------
1 | .form {
2 | display: flex;
3 | -ms-flex-direction: column;
4 | -webkit-flex-direction: column;
5 | flex-direction: column;
6 | }
7 |
--------------------------------------------------------------------------------
/src/app/components/shared/edit-business-account/edit-business-account.component.html:
--------------------------------------------------------------------------------
1 | {{title}}
2 |
3 |
4 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/src/app/components/shared/edit-business-account/edit-business-account.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, Inject, Optional } from '@angular/core';
2 | import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material';
3 | import { ChatBotReferance, ChatServerConnection } from '../../../models/app.models';
4 | import { SettingsService } from '../../../services/settings.service';
5 | import { ChatFlowService } from '../../../services/chatflow.service';
6 | import { InfoDialogService } from '../../../services/info-dialog.service';
7 | import { ChatServerManagerComponent } from '../chat-server-manager/chat-server-manager.component';
8 | import * as models from '../../../models/chatflow.models';
9 | import { DataService } from "../../../services/data.service";
10 | import { GlobalsService } from '../../../services/globals.service';
11 | import { Router } from "@angular/router";
12 | import { FormControl, Validators } from '@angular/forms';
13 | import { BusinessAccount } from '../../../models/data.models';
14 |
15 | @Component({
16 | selector: 'app-edit-business-account',
17 | templateUrl: './edit-business-account.component.html',
18 | styleUrls: ['./edit-business-account.component.css']
19 | })
20 | export class EditBusinessAccountComponent implements OnInit {
21 | title: string = "Create business account";
22 | isCreate: boolean = false;
23 | constructor(
24 | private global: GlobalsService,
25 | private infoDialog: InfoDialogService,
26 | private dataService: DataService,
27 | private dialogRef: MatDialogRef,
28 | @Optional()
29 | @Inject(MAT_DIALOG_DATA)
30 | public data: BusinessAccount) {
31 | this.dialogRef.disableClose = true;
32 |
33 | if (data) {
34 | this.account = data;
35 | this.title = "Edit business account";
36 | }
37 | if (!this.account.id) {
38 | this.account.id = this.global.uuidv4();
39 | this.isCreate = true;
40 | }
41 | }
42 |
43 | ngOnInit(): void {
44 |
45 | }
46 |
47 | account: BusinessAccount = {
48 | colors: [],
49 | email: "",
50 | logoUrl: "",
51 | name: "",
52 | phone: "",
53 | };
54 |
55 | primaryBGColor: string = "#8cc83c";
56 | primaryFGColor: string = "white";
57 | secondaryColor: string = "#3c3c3c";
58 |
59 | emailValid(val: string) {
60 | let r = this.global.emailValid(val);
61 | return r;
62 | }
63 | phoneValid(val: string) {
64 | return this.global.phoneValid(val);
65 | }
66 | pwdMatch(p1, p2) {
67 | return this.global.pwdMatch(p1, p2);
68 | }
69 |
70 | confirmPassword: string;
71 | save() {
72 | if (this.account.email) {
73 | if (!this.global.emailValid(this.account.email)) {
74 | this.infoDialog.alert("Invalid email address", "Please enter a valid email address");
75 | return;
76 | }
77 | }
78 |
79 | if (this.account.phone) {
80 | if (!this.global.phoneValid(this.account.phone)) {
81 | this.infoDialog.alert("Invalid phone number", "Please enter a valid phone number");
82 | return;
83 | }
84 | }
85 |
86 | this.account.colors = [
87 | {
88 | name: "PRIMARY_BG",
89 | value: this.primaryBGColor
90 | },
91 | {
92 | name: "PRIMARY_FG",
93 | value: this.primaryFGColor
94 | },
95 | {
96 | name: "SECONDARY",
97 | value: this.secondaryColor
98 | }
99 | ];
100 | this.infoDialog.showSpinner();
101 |
102 | this.dataService.saveBusinessAccount(this.account, this.isCreate).subscribe(x => {
103 | this.infoDialog.hideSpinner();
104 | if (x.success) {
105 | this.infoDialog.alert("Done", "Business account has been saved successfully", () => {
106 | this.dialogRef.close(true);
107 | });
108 | } else {
109 | this.dataService.handleTypedError(x.error, "Unable to save business account", "Something went wrong while trying to save business account details");
110 | }
111 | }, err => {
112 | this.infoDialog.hideSpinner();
113 | this.dataService.handleError(err, "Unable to save business account", "Something went wrong while trying to save business account details");
114 | });
115 | }
116 | }
--------------------------------------------------------------------------------
/src/app/components/shared/get-ana-chat-server/get-ana-chat-server.component.css:
--------------------------------------------------------------------------------
1 | .button-row {
2 | display: flex;
3 | align-items: center;
4 | justify-content: center;
5 | width: 100%;
6 | }
7 |
8 | .button-row > .mat-raised-button {
9 | margin-left: 10px;
10 | margin-right: 10px;
11 | height: 180px;
12 | width: 200px;
13 | font-size: 18px;
14 | }
15 |
16 | .button-row > .mat-raised-button:first-child {
17 | margin-left: 0;
18 | }
19 |
20 | .button-row > .mat-raised-button:last-child {
21 | margin-right: 0;
22 | }
23 |
24 | mat-raised-button svg {
25 | width: 84px;
26 | height: 84px;
27 | margin-left: 4px;
28 | }
29 |
30 | .actions {
31 | justify-content: flex-end;
32 | }
33 |
--------------------------------------------------------------------------------
/src/app/components/shared/get-ana-chat-server/get-ana-chat-server.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 | import { ElectronService } from 'ngx-electron';
3 | import { MatDialog } from '@angular/material';
4 | import { AnaCloudSignupComponent } from '../ana-cloud-signup/ana-cloud-signup.component';
5 | @Component({
6 | selector: 'app-get-ana-chat-server',
7 | templateUrl: './get-ana-chat-server.component.html',
8 | styleUrls: ['./get-ana-chat-server.component.css']
9 | })
10 | export class GetAnaChatServerComponent implements OnInit {
11 |
12 | constructor(
13 | private electron: ElectronService,
14 | private dialog: MatDialog,
15 | ) { }
16 |
17 | ngOnInit() {
18 | }
19 |
20 | selfHost() {
21 | this.electron.shell.openExternal('https://www.ana.chat/self-hosting.html?r=' + Math.random());
22 | }
23 |
24 | anaCloud() {
25 | let d = this.dialog.open(AnaCloudSignupComponent, {
26 | width: 'auto'
27 | });
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/app/components/shared/info-dialog/info-dialog.component.css:
--------------------------------------------------------------------------------
1 | mat-dialog-actions {
2 | justify-content: center;
3 | }
4 |
5 | mat-form-field {
6 | width: 100%
7 | }
8 |
--------------------------------------------------------------------------------
/src/app/components/shared/info-dialog/info-dialog.component.html:
--------------------------------------------------------------------------------
1 | {{options.title}}
2 |
3 | {{options.message}}
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/src/app/components/shared/info-dialog/info-dialog.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, Inject } from '@angular/core';
2 | import { MatDialog, MatDialogRef, MAT_DIALOG_DATA, MatSnackBar } from '@angular/material';
3 |
4 | @Component({
5 | selector: 'app-info-dialog',
6 | templateUrl: './info-dialog.component.html',
7 | styleUrls: ['./info-dialog.component.css']
8 | })
9 | export class InfoDialogComponent implements OnInit {
10 |
11 | constructor(
12 | private dialogRef: MatDialogRef,
13 | @Inject(MAT_DIALOG_DATA) public options: InfoDialogOptions) {
14 | this.dialogRef.disableClose = true;
15 |
16 | if (!options) {
17 | options = {
18 | dialogType: InfoDialogType.Alert,
19 | title: 'Title',
20 | message: 'Message'
21 | }
22 | }
23 |
24 | switch (options.dialogType) {
25 | case InfoDialogType.Confirm:
26 | {
27 | this.primaryButtonText = "OK";
28 | this.secondaryButtonText = "Cancel";
29 | }
30 | break;
31 | case InfoDialogType.Prompt:
32 | {
33 | this.inputText = options.defaultInputText;
34 | this.primaryButtonText = "Done";
35 | this.secondaryButtonText = "Cancel";
36 | }
37 | break;
38 | case InfoDialogType.Alert:
39 | default:
40 | {
41 | this.secondaryButtonText = "Close";
42 | }
43 | break;
44 | }
45 | }
46 |
47 | ngOnInit() {
48 | }
49 |
50 | inputKeypress(event: KeyboardEvent) {
51 | if (event.keyCode == 13) {
52 | this.primaryClick();
53 | }
54 | }
55 |
56 | primaryClick() {
57 | switch (this.options.dialogType) {
58 | case InfoDialogType.Confirm:
59 | this.dialogRef.close(true);
60 | break;
61 | case InfoDialogType.Prompt:
62 | this.dialogRef.close(this.inputText);
63 | break;
64 | case InfoDialogType.Alert:
65 | default:
66 | this.dialogRef.close();
67 | break;
68 | }
69 | }
70 | inputText: string;
71 | primaryButtonText: string;
72 | secondaryButtonText: string;
73 |
74 | InfoDialogType = InfoDialogType;
75 | }
76 |
77 | export interface InfoDialogOptions {
78 | dialogType: InfoDialogType,
79 | title: string,
80 | message: string,
81 | defaultInputText?: string,
82 | }
83 |
84 | export enum InfoDialogType {
85 | Prompt,
86 | Alert,
87 | Confirm
88 | }
--------------------------------------------------------------------------------
/src/app/components/shared/loading-indicator/loading-indicator.component.css:
--------------------------------------------------------------------------------
1 | .chatflow-loading {
2 | z-index: 20;
3 | padding: 10px 50px;
4 | position: fixed;
5 | top: 0;
6 | right: 0;
7 | bottom: 0;
8 | left: 0;
9 | background-color: #8cc83c;
10 | }
11 |
12 | .chatflow-loading > .loading-content {
13 | width: 200px;
14 | height: 200px;
15 | position: fixed;
16 | top: 40%;
17 | left: 50%;
18 | -moz-transform: translate(-50%,-50%);
19 | -ms-transform: translate(-50%,-50%);
20 | -o-transform: translate(-50%,-50%);
21 | -webkit-transform: translate(-50%,-50%);
22 | transform: translate(-50%,-50%);
23 | }
24 |
--------------------------------------------------------------------------------
/src/app/components/shared/loading-indicator/loading-indicator.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |

4 |
5 |
27 |
28 |
--------------------------------------------------------------------------------
/src/app/components/shared/loading-indicator/loading-indicator.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-loading-indicator',
5 | templateUrl: './loading-indicator.component.html',
6 | styleUrls: ['./loading-indicator.component.css']
7 | })
8 | export class LoadingIndicatorComponent implements OnInit {
9 |
10 | constructor() { }
11 |
12 | ngOnInit() {
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/src/app/components/shared/loading-mask/loading-mask.component.css:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nowfloats/ProjectAna/def5c4c9148fd72e59fe235eb34a3cd9a6a6aff3/src/app/components/shared/loading-mask/loading-mask.component.css
--------------------------------------------------------------------------------
/src/app/components/shared/loading-mask/loading-mask.component.html:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/components/shared/loading-mask/loading-mask.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit } from '@angular/core';
2 |
3 | @Component({
4 | selector: 'app-loading-mask',
5 | templateUrl: './loading-mask.component.html',
6 | styleUrls: ['./loading-mask.component.css']
7 | })
8 | export class LoadingMaskComponent implements OnInit {
9 |
10 | constructor() { }
11 |
12 | ngOnInit() {
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/src/app/components/shared/login/login.component.css:
--------------------------------------------------------------------------------
1 | mat-form-field {
2 | width: 100%;
3 | }
4 |
5 | mat-dialog-actions > .spacing {
6 | flex-grow: 1;
7 | }
8 |
--------------------------------------------------------------------------------
/src/app/components/shared/login/login.component.html:
--------------------------------------------------------------------------------
1 | Login
2 |
3 |
4 |
5 | Looks like you don't have any saved Ana chat server connections.
6 |
7 |
8 |
9 |
10 |
11 |
12 | 0">
13 |
14 |
15 | {{ conn.Name }}
16 |
17 |
18 |
19 |
20 |
21 |
30 |
31 |
32 |
33 |
34 | 0">
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/src/app/components/shared/login/login.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, Inject } from '@angular/core';
2 | import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material';
3 | import { ChatBotReferance, ChatServerConnection } from '../../../models/app.models';
4 | import { SettingsService } from '../../../services/settings.service';
5 | import { ChatFlowService } from '../../../services/chatflow.service';
6 | import { InfoDialogService } from '../../../services/info-dialog.service';
7 | import { ChatServerManagerComponent } from '../chat-server-manager/chat-server-manager.component';
8 | import * as models from '../../../models/chatflow.models';
9 | import { DataService } from "../../../services/data.service";
10 | import { Router } from "@angular/router";
11 | @Component({
12 | selector: 'app-login',
13 | templateUrl: './login.component.html',
14 | styleUrls: ['./login.component.css']
15 | })
16 | export class LoginComponent implements OnInit {
17 |
18 | constructor(
19 | private settings: SettingsService,
20 | private chatFlowService: ChatFlowService,
21 | private dialog: MatDialog,
22 | private infoDialog: InfoDialogService,
23 | private dataService: DataService,
24 | private router: Router,
25 | private dialogRef: MatDialogRef) {
26 |
27 | this.dialogRef.disableClose = true;
28 |
29 | this.loadSavedConns();
30 | //this.dialogRef.afterClosed().subscribe(x => {
31 | // if (!this.dataService.loggedInUser)
32 | // this.router.navigateByUrl('/');
33 | //});
34 | }
35 |
36 | loadSavedConns() {
37 | this.savedConns = this.settings.loadSavedConnections();
38 | if (this.savedConns.length == 1)
39 | this.selectedServer = this.savedConns[0];
40 | else
41 | this.selectedServer = null;
42 | }
43 |
44 | savedConns: ChatServerConnection[] = [];
45 | selectedServer: ChatServerConnection;
46 |
47 | username: string;
48 | password: string;
49 |
50 | ngOnInit() {
51 |
52 | }
53 |
54 | dismiss() {
55 | this.dialogRef.close();
56 | }
57 |
58 | login() {
59 | this.dataService.loggedInUser = null;
60 | this.dataService.setConnection(this.selectedServer);
61 | this.infoDialog.showSpinner();
62 | this.dataService.login(this.username, this.password).subscribe(x => {
63 | this.infoDialog.hideSpinner();
64 | if (x.success) {
65 | this.dataService.loggedInUser = x.data;
66 | localStorage.setItem("user", JSON.stringify(x.data));
67 | this.dialogRef.close(true);
68 | } else
69 | this.dataService.handleTypedError(x.error, "Oops! Unable to login.", "Something went wrong while trying to login. Please try again.");
70 | }, err => {
71 | this.infoDialog.hideSpinner();
72 | this.dataService.handleError(err, "Oops! Unable to login.", "Something went wrong while trying to login. Please try again.");
73 | });
74 | }
75 |
76 | managePublishServers() {
77 | let dialogRef = this.dialog.open(ChatServerManagerComponent, {
78 | width: '60%',
79 | });
80 |
81 | dialogRef.afterClosed().subscribe(x => {
82 | this.loadSavedConns();
83 | });
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/app/components/shared/publish-chatbot/publish-chatbot.component.css:
--------------------------------------------------------------------------------
1 | mat-form-field {
2 | width: 100%;
3 | }
4 |
5 | mat-dialog-actions > .spacing {
6 | flex-grow: 1;
7 | }
8 |
9 | .desc {
10 | font-size: 11px;
11 | opacity: 0.6;
12 | }
13 |
14 | label.desc {
15 | margin-top: 10px;
16 | }
--------------------------------------------------------------------------------
/src/app/components/shared/publish-chatbot/publish-chatbot.component.html:
--------------------------------------------------------------------------------
1 | Publish chatbot
2 |
3 |
4 | Looks like you don't have any Ana chatbot projects yet.
5 |
6 | 0" #chatProjectFormField tabindex="-1">
7 |
8 |
9 |
10 | {{ option.name }} Last updated at {{option.updatedAt | date:'hh:mm a on dd-MM-yyyy'}} -- Id: {{ option.id }}
11 |
12 |
13 |
14 | {{added}}
15 |
16 |
17 | 0" class="text-center">
18 | Or
19 |
20 |
21 |
22 |
Create new chatbot project
23 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
--------------------------------------------------------------------------------
/src/app/components/shared/publish-dialog/publish-dialog.component.css:
--------------------------------------------------------------------------------
1 | mat-form-field {
2 | width: 100%;
3 | }
4 |
5 | mat-dialog-actions > .spacing {
6 | flex-grow: 1;
7 | }
8 |
--------------------------------------------------------------------------------
/src/app/components/shared/publish-dialog/publish-dialog.component.html:
--------------------------------------------------------------------------------
1 | Publish chatbot
2 |
3 |
4 |
5 | Looks like you don't have any saved Ana chat server connections.
6 |
7 |
8 |
9 |
10 |
11 |
12 | 0">
13 |
14 |
15 | {{ conn.Name }}
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 | {{ proj.Name }}
24 |
25 |
26 |
27 |
28 |
29 | 0">
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/src/app/components/shared/publish-dialog/publish-dialog.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, Inject } from '@angular/core';
2 | import { MatDialog, MatDialogRef, MAT_DIALOG_DATA } from '@angular/material';
3 | import { ChatBotProject, ChatBotReferance, ChatServerConnection } from '../../../models/app.models';
4 | import { SettingsService } from '../../../services/settings.service';
5 | import { ChatFlowService } from '../../../services/chatflow.service';
6 | import { InfoDialogService } from '../../../services/info-dialog.service';
7 | import { ChatServerManagerComponent } from '../chat-server-manager/chat-server-manager.component';
8 | import * as models from '../../../models/chatflow.models';
9 | @Component({
10 | selector: 'app-publish-dialog',
11 | templateUrl: './publish-dialog.component.html',
12 | styleUrls: ['./publish-dialog.component.css']
13 | })
14 | export class PublishDialogComponent implements OnInit {
15 |
16 | constructor(
17 | private settings: SettingsService,
18 | private chatFlowService: ChatFlowService,
19 | private dialog: MatDialog,
20 | private infoDialog: InfoDialogService,
21 | private dialogRef: MatDialogRef,
22 | @Inject(MAT_DIALOG_DATA) private pack: models.ChatFlowPack) {
23 | this.loadSavedConns();
24 | }
25 |
26 | loadSavedConns() {
27 | this.savedConns = this.settings.loadSavedConnections();
28 | this.selectedServer = null;
29 | this.selectedProject = null;
30 | }
31 |
32 | savedConns: ChatServerConnection[] = [];
33 | chatProjects: ChatBotProject[] = [];
34 | selectedServer: ChatServerConnection;
35 | selectedProject: ChatBotProject;
36 |
37 | ngOnInit() {
38 | }
39 |
40 | publish() {
41 | this.infoDialog.showSpinner();
42 | this.chatFlowService.chatProjectExists(this.selectedServer, this.selectedProject).subscribe(x => {
43 | this.infoDialog.hideSpinner();
44 | this.infoDialog.confirm("Sure?", `Chat project with id '${this.selectedProject.Id}' already exists. Publishing this will overwrite it. Do you want to proceed?`, (ok) => {
45 | if (ok)
46 | this.doPublish();
47 | });
48 | }, err => {
49 | this.infoDialog.hideSpinner();
50 | this.doPublish();
51 | });
52 | }
53 |
54 | private doPublish() {
55 | this.infoDialog.showSpinner();
56 | this.chatFlowService.publishProject(this.selectedServer, this.selectedProject, this.pack).subscribe(x => {
57 | this.infoDialog.hideSpinner();
58 | this.infoDialog.alert('Done', 'Chatbot published successfully', () => this.dismiss());
59 | }, err => {
60 | this.infoDialog.hideSpinner();
61 | this.infoDialog.alert('Done', 'Oops! Something went wrong while publishing the chat project! Please try again.');
62 | });
63 | }
64 |
65 | managePublishServers() {
66 | let dialogRef = this.dialog.open(ChatServerManagerComponent, {
67 | width: '60%',
68 | });
69 |
70 | dialogRef.afterClosed().subscribe(x => {
71 | this.loadSavedConns();
72 | });
73 | }
74 | dismiss() {
75 | this.dialogRef.close();
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/app/components/shared/update-password/update-password.component.css:
--------------------------------------------------------------------------------
1 | .form {
2 | display: flex;
3 | -ms-flex-direction: column;
4 | -webkit-flex-direction: column;
5 | flex-direction: column;
6 | }
7 |
8 | mat-dialog-actions button {
9 | margin-left: 0 !important;
10 | margin-right: 8px !important;
11 | }
--------------------------------------------------------------------------------
/src/app/components/shared/update-password/update-password.component.html:
--------------------------------------------------------------------------------
1 | Update Password
2 |
3 |
4 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/src/app/components/shared/update-password/update-password.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, Optional, Inject } from '@angular/core';
2 | import { GlobalsService } from '../../../services/globals.service';
3 | import { InfoDialogService } from '../../../services/info-dialog.service';
4 | import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material';
5 | import { DataService } from '../../../services/data.service';
6 | import { User } from '../../../models/data.models';
7 |
8 | @Component({
9 | selector: 'app-update-password',
10 | templateUrl: './update-password.component.html',
11 | styleUrls: ['./update-password.component.css']
12 | })
13 | export class UpdatePasswordComponent implements OnInit {
14 |
15 | constructor(
16 | private global: GlobalsService,
17 | private infoDialog: InfoDialogService,
18 | private dataService: DataService,
19 | private dialogRef: MatDialogRef,
20 | @Optional()
21 | @Inject(MAT_DIALOG_DATA)
22 | public user: User) {
23 |
24 | }
25 |
26 | password: string;
27 | confirmPassword: string;
28 |
29 | ngOnInit() {
30 | }
31 |
32 | save() {
33 | if (!this.global.pwdMatch(this.password, this.confirmPassword)) {
34 | this.infoDialog.alert("Passwords do not match or not secure", "Please ensure the password and confirm password is same. Also, a password must be at least 6 characters.");
35 | return;
36 | }
37 | this.infoDialog.showSpinner();
38 | this.dataService.updatePassword(this.user.id, this.password).subscribe(x => {
39 | this.infoDialog.hideSpinner();
40 | if (x.success) {
41 | this.infoDialog.alert("Password updated", "Password has been updated successfully", () => {
42 | this.dialogRef.close();
43 | });
44 | } else {
45 | this.dataService.handleTypedError(x.error, "Unable to update password", "Something went wrong while trying to update the password.")
46 | }
47 | }, err => {
48 | this.infoDialog.hideSpinner();
49 | this.dataService.handleError(err, "Unable to update password", "Something went wrong while trying to update the password.")
50 | });
51 | }
52 | }
--------------------------------------------------------------------------------
/src/app/components/studio/chatflow/chatflow.component.css:
--------------------------------------------------------------------------------
1 | /** {
2 | position: static !important;
3 | top: auto !important;
4 | }*/
5 | .pointer-events-none {
6 | pointer-events: none;
7 | }
8 |
9 | .pointer-cursor {
10 | cursor: pointer;
11 | }
12 |
13 | .not-allowed-cursor {
14 | cursor: not-allowed;
15 | }
16 |
17 | .moving-cursor {
18 | cursor: grab;
19 | }
20 |
21 | .section-icon {
22 | width: 23px;
23 | }
24 |
25 | .chatflow-actions {
26 | margin: 15px 0;
27 | z-index: 10;
28 | display: flex;
29 | flex-direction: column;
30 | }
31 |
32 | .chatflow-actions > button {
33 | margin: 8px 0;
34 | font-size: 12px;
35 | }
36 |
37 | .chatflow-actions .fab-btn-icon {
38 | margin-bottom: -13px !important;
39 | }
40 |
41 | .chatflow-root-svg {
42 | overflow: hidden;
43 | width: calc(99vw - 88px);
44 | height: 99vh;
45 | }
46 |
47 | .chatflow-designer-wrapper {
48 | overflow: hidden;
49 | display: flex;
50 | -ms-flex-direction: row;
51 | -webkit-flex-direction: row;
52 | flex-direction: row;
53 | }
54 |
55 | .chatflow-designer-wrapper .left-sidebar {
56 | height: 100vh;
57 | background-color: #8cc83c;
58 | color: white;
59 | }
60 |
61 | .chatflow-designer-wrapper .chatflow-root-svg {
62 | flex-grow: 1;
63 | }
64 |
65 | .chatnode {
66 | background: none;
67 | cursor: move;
68 | border: 2px solid transparent;
69 | border-radius: 12px;
70 | }
71 |
72 | .chatnode:hover {
73 | border: 2px solid rgba(140, 200, 60, 0.50);
74 | }
75 |
76 | .chatnode.selected {
77 | border: 2px solid #8cc83c;
78 | }
79 |
80 | .chatnode.click-connect {
81 | cursor: alias;
82 | }
83 |
84 | .chatnode-header {
85 | height: 30px;
86 | border-radius: 10px 10px 0 0;
87 | background-color: #666666;
88 | }
89 |
90 | .chatnode-header.startnode {
91 | background-color: #8cc83c;
92 | }
93 |
94 | .chatnode-header > p {
95 | padding: 5px;
96 | text-align: center;
97 | color: white
98 | }
99 |
100 | .chatnode-body {
101 | background-color: white;
102 | color: #666666;
103 | border-radius: 0 0 10px 10px;
104 | }
105 |
106 | .chatnode-sections {
107 | height: 100%;
108 | padding: 0 5px
109 | }
110 |
111 | .chatnode-sections > table {
112 | width: 100%;
113 | }
114 |
115 | .chatnode-sections > table > tr {
116 | border-bottom: 1px dashed #F0F0F0;
117 | }
118 |
119 | .chatnode-sections > table > tr > td {
120 | padding-left: 5px;
121 | }
122 |
123 | .section-icon > fa {
124 | font-size: 20px;
125 | }
126 |
127 | .section-alias {
128 | padding-right: 5px
129 | }
130 |
131 | .section-alias > div {
132 | margin: 10px 0px 10px 5px
133 | }
134 |
135 | .chatnode-buttons {
136 | height: 100%;
137 | text-align: center;
138 | width: 100%;
139 | margin-top: 10px
140 | }
141 |
142 | .chatnode-buttons > table {
143 | width: 100%;
144 | table-layout: fixed;
145 | }
146 |
147 | .chatnode-buttons > table > tr > td {
148 | width: 80px;
149 | padding: 10px;
150 | }
151 |
152 | .chatnode-buttons > table > tr > td > div {
153 | text-align: center;
154 | }
155 |
156 | .chatnode-buttons > table > tr > td > div > .fa {
157 | font-size: 20px;
158 | }
159 |
160 | .chatnode-empty {
161 | text-align: center;
162 | width: 100%;
163 | padding: 5px 0 10px 0;
164 | }
165 |
--------------------------------------------------------------------------------
/src/app/components/studio/landing/landing.component.css:
--------------------------------------------------------------------------------
1 | :host {
2 | width: 100%;
3 | }
4 |
5 | mat-form-field, mat-select {
6 | width: 100%;
7 | }
8 |
9 | .bottom-actions > button {
10 | margin: 20px 10px;
11 | }
12 |
13 | button[color="primary"] {
14 | color: white;
15 | }
16 |
17 | mat-action-row > button {
18 | margin: 10px;
19 | }
20 |
21 | .bottom-actions {
22 | display: block;
23 | text-align: center;
24 | }
25 |
26 | .list-action-btn {
27 | float: right;
28 | }
29 |
30 | .list-item {
31 | width: 100%
32 | }
33 |
34 | .mat-expansion-panel-body {
35 | display: none !important;
36 | }
37 |
38 | .app-content {
39 | width: 700px;
40 | margin-left: auto;
41 | margin-right: auto;
42 | margin-top: 40px;
43 | padding-bottom: 40px;
44 | }
45 |
46 | .page-title {
47 | display: flex;
48 | flex-direction: row;
49 | }
50 |
51 | .page-title .text {
52 | flex-grow: 1;
53 | }
54 |
55 | .page-title-actions button:first-child {
56 | margin-left: 10px;
57 | margin-right: 10px;
58 | }
59 |
--------------------------------------------------------------------------------
/src/app/components/studio/landing/landing.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Looks like you don't have any saved chat projects. Start by adding one.
7 |
8 |
9 |
10 |
11 |
Recent chatbot projects
12 |
13 |
14 |
15 |
16 |
17 |
18 |
21 |
24 |
25 |
26 |
27 |
28 | {{projName}}
29 |
30 |
31 |
34 |
37 |
40 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/src/app/components/studio/landing/landing.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, ViewChild, ElementRef } from '@angular/core';
2 | import { Router } from '@angular/router';
3 | import { InfoDialogService } from '../../../services/info-dialog.service';
4 | import { GlobalsService } from '../../../services/globals.service';
5 | import { SettingsService } from '../../../services/settings.service';
6 | import * as models from '../../../models/chatflow.models';
7 | import { ObjectID } from 'bson';
8 |
9 | @Component({
10 | selector: 'app-studio-landing',
11 | templateUrl: './landing.component.html',
12 | styleUrls: ['./landing.component.css']
13 | })
14 | export class LandingComponent implements OnInit {
15 |
16 | constructor(
17 | private router: Router,
18 | private globals: GlobalsService,
19 | private infoDialog: InfoDialogService,
20 | private settings: SettingsService) {
21 | this.globals.setPageTitle();
22 | this.loadSavedProjects();
23 | }
24 |
25 | @ViewChild('fileInput')
26 | fileInput: ElementRef;
27 |
28 | loadSavedProjects() {
29 | this.savedProjects = this.settings.listSavedChatProjectNames();
30 | }
31 |
32 | savedProjects: string[] = [];
33 |
34 | ngOnInit() {
35 |
36 | }
37 |
38 | fileInputChange() {
39 | let fileInput = this.fileInput.nativeElement as HTMLInputElement;
40 | if (fileInput.files && fileInput.files[0]) {
41 | let selectedFile = fileInput.files[0];
42 | fileInput.value = '';
43 | if (selectedFile.name.endsWith('.anaproj')) {
44 | let reader = new FileReader();
45 | reader.onload = (evt) => {
46 | let pack = JSON.parse(reader.result) as models.ChatFlowPack;
47 | let projName = selectedFile.name.replace(new RegExp('\.anaproj$'), '');
48 | this.settings.saveChatProject(projName, pack, false, () => {
49 | this.openChatBotProject(projName);
50 | });
51 | };
52 | reader.onerror = () => {
53 | this.infoDialog.alert('Oops!', 'Unable to load the file!');
54 | };
55 | reader.readAsText(selectedFile, "UTF-8");
56 | } else
57 | this.infoDialog.alert('Oops!', 'Invalid file. Please select a valid Ana project file');
58 | }
59 | }
60 | search: string;
61 | searchedProjects() {
62 | if (this.search && this.search.length > 0)
63 | return this.savedProjects.filter(x => x.toLowerCase().indexOf(this.search.toLowerCase()) != -1);
64 | return this.savedProjects;
65 | }
66 | addProject() {
67 | this.infoDialog.prompt('Chatbot Project Name', 'Enter a name for your new chatbot project', (name) => {
68 | if (!name)
69 | return;
70 |
71 | let firstNode = {
72 | Name: 'New Node',
73 | Id: new ObjectID().toHexString(),
74 | Buttons: [],
75 | Sections: [],
76 | NodeType: models.NodeType.Combination,
77 | TimeoutInMs: 0
78 | };
79 | let _id = new ObjectID().toHexString();
80 | let defaultFlow: models.ChatFlowPack = {
81 | ChatNodes: [firstNode],
82 | CreatedOn: new Date(),
83 | UpdatedOn: new Date(),
84 | NodeLocations: {},
85 | ProjectId: _id,
86 | _id: _id
87 | };
88 | defaultFlow.NodeLocations[firstNode.Id] = { X: 500, Y: 500 };
89 | this.settings.saveChatProject(name, defaultFlow, false, () => {
90 | this.openChatBotProject(name);
91 | });
92 | });
93 | }
94 |
95 | isExpanded(proj: string) {
96 | return this.savedProjects.indexOf(proj) == this.savedProjects.length - 1;
97 | }
98 |
99 | openChatBotProject(name: string) {
100 | this.router.navigateByUrl('/studio/designer?proj=' + encodeURIComponent(name));
101 | }
102 | renameChatBotProject(name: string) {
103 | this.infoDialog.prompt("Rename", 'Enter a new name: ', (newName) => {
104 | if (newName && name != newName) {
105 | this.settings.renameChatProject(name, newName);
106 | this.loadSavedProjects();
107 | }
108 | }, name);
109 | }
110 | deleteChatBotProject(name: string) {
111 | this.infoDialog.confirm('Sure?', `Are you sure you want to delete '${name}'`, (ok) => {
112 | if (ok) {
113 | this.settings.deleteChatProject(name);
114 | this.loadSavedProjects();
115 | }
116 | });
117 | }
118 |
119 | downloadChatBotProject(name: string) {
120 | let pack = this.settings.getChatProject(name);
121 | this.globals.downloadTextAsFile(name + ".anaproj", JSON.stringify(pack));
122 | }
123 |
124 | }
--------------------------------------------------------------------------------
/src/app/components/studio/nodeeditor/nodeeditor.component.css:
--------------------------------------------------------------------------------
1 | mat-form-field, mat-select {
2 | width: 100%;
3 | }
4 |
5 | .text-section-icon {
6 | font-size: 20px;
7 | }
8 |
9 | .tab-content {
10 | margin: 20px;
11 | }
12 |
13 | .col-centered {
14 | float: none;
15 | margin: 0 auto;
16 | }
17 |
18 | .panel-header-icon {
19 | margin-top: 2px;
20 | margin-right: 5px;
21 | }
22 |
23 | .spacing {
24 | width: 20px;
25 | display: inline-block;
26 | }
27 |
28 | .dialog-actions {
29 | margin: 10px;
30 | }
31 |
32 | mat-dialog-actions {
33 | justify-content: center;
34 | }
35 |
36 | .section-type-button {
37 | width: 110px;
38 | height: 110px;
39 | padding: 11px;
40 | cursor: pointer;
41 | }
42 |
43 | .section-type-button.text {
44 | content: url("../../../../assets/img/node-editor/text.png");
45 | }
46 |
47 | .section-type-button.text:hover {
48 | content: url("../../../../assets/img/node-editor/text-hover.png");
49 | padding: 0;
50 | }
51 |
52 | .section-type-button.image {
53 | content: url("../../../../assets/img/node-editor/image.png");
54 | }
55 |
56 | .section-type-button.image:hover {
57 | content: url("../../../../assets/img/node-editor/image-hover.png");
58 | padding: 0;
59 | }
60 |
61 | .section-type-button.video {
62 | content: url("../../../../assets/img/node-editor/video.png");
63 | }
64 |
65 | .section-type-button.video:hover {
66 | content: url("../../../../assets/img/node-editor/video-hover.png");
67 | padding: 0;
68 | }
69 |
70 | .section-type-button.gif {
71 | content: url("../../../../assets/img/node-editor/gif.png");
72 | }
73 |
74 | .section-type-button.gif:hover {
75 | content: url("../../../../assets/img/node-editor/gif-hover.png");
76 | padding: 0;
77 | }
78 |
79 | .section-type-button.carousel {
80 | content: url("../../../../assets/img/node-editor/carousel.png");
81 | }
82 |
83 | .section-type-button.carousel:hover {
84 | content: url("../../../../assets/img/node-editor/carousel-hover.png");
85 | padding: 0;
86 | }
87 |
--------------------------------------------------------------------------------
/src/app/components/studio/nodeeditor/nodeeditor.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, Inject, OnInit, ViewChild, OnDestroy, AfterViewInit } from '@angular/core';
2 | import { HttpClient } from '@angular/common/http';
3 | import { ActivatedRoute } from '@angular/router';
4 | import { MatDialog, MatDialogRef, MAT_DIALOG_DATA, MatTab, MatTabGroup } from '@angular/material';
5 | import 'rxjs/add/operator/filter';
6 |
7 | import * as models from '../../../models/chatflow.models';
8 | import * as chatflow from '../../../components/studio/chatflow/chatflow.component';
9 | import { ChatFlowService } from '../../../services/chatflow.service';
10 | import { GlobalsService } from '../../../services/globals.service';
11 | import { InfoDialogService } from '../../../services/info-dialog.service';
12 | import { Hotkey, HotkeysService } from 'angular2-hotkeys';
13 |
14 | @Component({
15 | selector: 'app-nodeeditor',
16 | templateUrl: './nodeeditor.component.html',
17 | styleUrls: ['./nodeeditor.component.css']
18 | })
19 | export class NodeEditorComponent implements OnInit, OnDestroy {
20 | constructor(
21 | private chatFlowService: ChatFlowService,
22 | private hotkeys: HotkeysService,
23 | private infoDialog: InfoDialogService,
24 | public dialogRef: MatDialogRef,
25 | @Inject(MAT_DIALOG_DATA) public chatNode: models.ChatNode,
26 | public globalsService: GlobalsService) {
27 | this.MH = new models.ModelHelpers(globalsService, infoDialog);
28 | }
29 |
30 | @ViewChild("nodeInfoTab")
31 | nodeInfoTab: MatTab;
32 |
33 | @ViewChild("contentTab")
34 | contentTab: MatTab;
35 | contentTabIndex = 1;
36 |
37 | @ViewChild("buttonsTab")
38 | buttonsTab: MatTab;
39 |
40 | @ViewChild("tabGroup")
41 | tabGroup: MatTabGroup;
42 |
43 | keymapOnNodeEditor: Hotkey[] = [
44 | new Hotkey(["t", "alt+t"], (e, s) => {
45 | this.addNewSectionText();
46 | return false;
47 | }, [], "Add a new text content (if applicable)"),
48 | new Hotkey(["b", "alt+b"], (e, s) => {
49 | this.addNewButton();
50 | return false;
51 | }, [], "Add a new button")
52 | ];
53 |
54 | bindNodeEditorShortcuts() {
55 | this.unbindNodeEditorShortcuts();
56 | this.keymapOnNodeEditor.forEach(x => this.hotkeys.add(x));
57 | }
58 |
59 | unbindNodeEditorShortcuts() {
60 | this.keymapOnNodeEditor.forEach(x => this.hotkeys.remove(x));
61 | }
62 |
63 | ngOnDestroy(): void {
64 | this.unbindNodeEditorShortcuts();
65 | }
66 |
67 | ngOnInit(): void {
68 | this.bindNodeEditorShortcuts();
69 | if (this.chatNode && this.contentTabVisible() && this.chatNode.Sections.length > 0) {
70 | this.tabGroup.selectedIndex = this.contentTabIndex;
71 | }
72 | }
73 |
74 | contentTabVisible() {
75 | return ['Card', 'Combination'].indexOf(this.chatNode.NodeType) != -1;
76 | }
77 |
78 | addNewSectionText() {
79 | if (this.contentTab) {
80 | let newIndex = this.tabGroup._tabs.toArray().findIndex(x => x.textLabel == this.contentTab.textLabel);
81 | if (this.tabGroup.selectedIndex == newIndex) {
82 | this.MH.addSection(this.chatNode, models.SectionType.Text);
83 | } else {
84 | this.tabGroup.selectedIndex = newIndex;
85 | setTimeout(() => {
86 | requestAnimationFrame(() => {
87 | this.MH.addSection(this.chatNode, models.SectionType.Text);
88 | });
89 | }, 500);
90 | }
91 | }
92 | }
93 |
94 | addNewButton() {
95 | if (this.buttonsTab) {
96 | let newIndex = this.tabGroup._tabs.toArray().findIndex(x => x.textLabel == this.buttonsTab.textLabel);
97 | if (this.tabGroup.selectedIndex == newIndex) {
98 | this.MH.addButton(this.chatNode);
99 | } else {
100 | this.tabGroup.selectedIndex = newIndex;
101 | setTimeout(() => {
102 | requestAnimationFrame(() => {
103 | this.MH.addButton(this.chatNode);
104 | });
105 | }, 500);
106 | }
107 | }
108 | }
109 |
110 | SectionType = models.SectionType;
111 | dismiss() {
112 | this.dialogRef.close();
113 | }
114 | //Model Helpers
115 | MH: models.ModelHelpers;
116 | }
117 |
--------------------------------------------------------------------------------
/src/app/components/studio/simulator-frame/simulator-frame.component.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
12 |
15 |
16 |
17 |
18 |
29 |
30 |
--------------------------------------------------------------------------------
/src/app/components/studio/simulator-frame/simulator-frame.component.ts:
--------------------------------------------------------------------------------
1 | import { Component, OnInit, ViewChild, ElementRef } from '@angular/core';
2 | import { SafeResourceUrl, DomSanitizer } from '@angular/platform-browser';
3 | import { environment } from '../../../../environments/environment';
4 |
5 | @Component({
6 | selector: 'app-simulator-frame',
7 | templateUrl: './simulator-frame.component.html',
8 | styleUrls: ['./simulator-frame.component.css']
9 | })
10 | export class SimulatorFrameComponent implements OnInit {
11 |
12 | constructor(private sanitizer: DomSanitizer) {
13 | let param = {
14 | brandingConfig: {
15 | primaryBackgroundColor: '#8cc83c',
16 | primaryForegroundColor: 'white',
17 | secondaryBackgroundColor: '#3c3c3c',
18 | logoUrl: `favicon.ico`,
19 | agentName: "ANA Simulator",
20 | frameHeight: '70vh',
21 | frameWidth: '360px',
22 | },
23 | simulatorMode: true,
24 | appConfig: {
25 | htmlMessages: true
26 | }
27 | };
28 | let url = `simulator/index.html?s=${btoa(JSON.stringify(param))}`;
29 | if (environment.local)
30 | url = `http://localhost:4200/index.html?s=${btoa(JSON.stringify(param))}`;
31 | this.iframeUrl = this.sanitizer.bypassSecurityTrustResourceUrl(url);
32 | }
33 |
34 | ngOnInit() {
35 |
36 | }
37 | iframeUrl: SafeResourceUrl;
38 | isOpen: boolean = false;
39 |
40 | @ViewChild('anaRoot')
41 | anaRoot: ElementRef;
42 |
43 | @ViewChild('simulatorIFrame')
44 | simulatorIFrame: ElementRef;
45 |
46 | frame() {
47 | return (this.simulatorIFrame.nativeElement as HTMLIFrameElement).contentWindow;
48 | }
49 |
50 | minMaxBtnClick() {
51 | this.isOpen = !this.isOpen;
52 | if (this.anaRoot && this.anaRoot.nativeElement)
53 | (this.anaRoot.nativeElement).classList.remove('ana-hidden');
54 | };
55 | }
56 |
--------------------------------------------------------------------------------
/src/app/components/studio/studio.module.ts:
--------------------------------------------------------------------------------
1 | import { NgModule, NO_ERRORS_SCHEMA } from '@angular/core';
2 | import { RouterModule, Routes } from '@angular/router';
3 | import { ChatFlowComponent } from './chatflow/chatflow.component';
4 | import { NodeEditorComponent } from './nodeeditor/nodeeditor.component';
5 | import { SimulatorFrameComponent } from './simulator-frame/simulator-frame.component';
6 | import { LandingComponent } from './landing/landing.component';
7 | import { SharedModule } from '../../shared.module';
8 |
9 | export const STUDIO_ROUTES: Routes = [
10 | {
11 | path: "",
12 | component: LandingComponent
13 | },
14 | {
15 | path: "designer",
16 | component: ChatFlowComponent
17 | }
18 | ];
19 |
20 | @NgModule({
21 | declarations: [
22 | ChatFlowComponent,
23 | NodeEditorComponent,
24 | SimulatorFrameComponent,
25 | LandingComponent,
26 | ],
27 | entryComponents: [
28 | NodeEditorComponent
29 | ],
30 | imports: [
31 | SharedModule
32 | ],
33 | schemas: [
34 | NO_ERRORS_SCHEMA
35 | ]
36 | })
37 | export class StudioModule { }
38 |
39 |
--------------------------------------------------------------------------------
/src/app/directives/autofocus.directive.ts:
--------------------------------------------------------------------------------
1 | import { Directive, ElementRef, Input } from '@angular/core';
2 |
3 | @Directive({
4 | selector: '[autofocus]'
5 | })
6 | export class AutofocusDirective {
7 | private _autofocus;
8 | constructor(private el: ElementRef) {
9 | }
10 |
11 | ngOnInit() {
12 | if (this._autofocus || typeof this._autofocus === "undefined")
13 | this.el.nativeElement.focus();
14 | }
15 |
16 | @Input() set autofocus(condition: boolean) {
17 | this._autofocus = condition != false;
18 | }
19 | }
--------------------------------------------------------------------------------
/src/app/models/app.models.ts:
--------------------------------------------------------------------------------
1 | export interface ChatServerConnection {
2 | Name: string;
3 | ServerUrl: string;
4 | APIKey: string;
5 | APISecret: string;
6 | IsDefault: boolean;
7 | ChatProjects: ChatBotProject[];
8 | }
9 |
10 | export interface ChatBotProject {
11 | Name: string;
12 | CreatedOn: Date;
13 | UpdatedOn: Date;
14 | Id: string;
15 | }
16 |
17 | export interface ChatBotReferance {
18 | Name: string;
19 | ServerUrl: string;
20 | APIKey: string;
21 | APISecret: string;
22 | IsDefault: boolean;
23 | }
24 |
25 |
26 |
--------------------------------------------------------------------------------
/src/app/models/data.models.ts:
--------------------------------------------------------------------------------
1 | export interface ErrorDetail {
2 | field: string;
3 | message: string;
4 | }
5 |
6 | export interface ErrorItem {
7 | code: string;
8 | status: number;
9 | message: string;
10 | timestamp: number;
11 | errors: ErrorDetail[];
12 | }
13 |
14 | export interface APIResponse {
15 | data?: TData;
16 | error?: ErrorItem;
17 | success: boolean;
18 | links: Link[];
19 | }
20 |
21 | export interface Link {
22 | href: string;
23 | rel: string;
24 | templated: boolean;
25 | }
26 | export interface Role {
27 | id: number;
28 | role: string;
29 | description: string;
30 | label: string;
31 | enabled: boolean;
32 | }
33 |
34 | export interface LoginData {
35 | userId: string;
36 | username: string;
37 | accessToken: string;
38 | name: string;
39 | businessId: string;
40 | roles: Role[];
41 | }
42 |
43 | export interface Color {
44 | id?: string;
45 | name: string;
46 | value: string;
47 | }
48 |
49 | export interface BusinessAccount {
50 | colors?: Color[];
51 | createdAt?: number;
52 | email: string;
53 | id?: string;
54 | logoUrl?: string;
55 | modifiedAt?: number;
56 | name: string;
57 | phone: string;
58 | status?: string;
59 | }
60 |
61 | export interface Sort {
62 | }
63 |
64 | export interface ListContent {
65 | content: TItem[];
66 | first: boolean;
67 | last: boolean;
68 | number: number;
69 | numberOfElements: number;
70 | size: number;
71 | sort: Sort;
72 | totalElements: number;
73 | totalPages: number;
74 | }
75 |
76 | export interface ListData {
77 | data: TItem[];
78 | success: boolean;
79 | }
80 |
81 | export enum BusinessAccountStatus {
82 | INACTIVE = 0,
83 | ACTIVE = 1,
84 | EXPIRED = 2,
85 | BLOCKED = 3,
86 | DELETED = 4
87 | }
88 |
89 | export enum DevicePlatform {
90 | ANDROID = 'ANDROID',
91 | IOS = 'IOS',
92 | WINDOWS = 'WINDOWS',
93 | FACEBOOK = 'FACEBOOK'
94 | }
95 |
96 | export enum DeviceStatus {
97 | ACTIVE = 'ACTIVE',
98 | INACTIVE = 'INACTIVE',
99 | BLOCKED = 'BLOCKED'
100 | }
101 |
102 | export interface Device {
103 | createdAt: Date;
104 | deviceId: string;
105 | devicePlatform: DevicePlatform;
106 | id: number;
107 | status: DeviceStatus;
108 | version: string;
109 | }
110 |
111 | export interface User {
112 | businessId: string;
113 | createdAt?: number;
114 | device?: Device;
115 | email: string;
116 | id: string;
117 | name: string;
118 | phone: string;
119 | roles: Role[];
120 | userName: string;
121 | }
122 |
123 | export interface UserRegisterModel {
124 | businessId?: string,
125 | email: string,
126 | name: string,
127 | password: string,
128 | phone: string,
129 | roleIds: number[]
130 | }
131 |
132 | export interface ChatProject {
133 | businessId: string;
134 | businessName?: string;
135 | createdAt?: number;
136 | flow?: any;
137 | id: string;
138 | name: string;
139 | source?: any;
140 | status?: string;
141 | updatedAt?: number;
142 | userId?: string;
143 | }
144 |
145 | export interface RegisterOnAnaCloudDetails {
146 | business: BusinessAccount;
147 | user: UserRegisterModel;
148 | }
149 |
--------------------------------------------------------------------------------
/src/app/pipes/ellipsis.pipe.ts:
--------------------------------------------------------------------------------
1 | import { Pipe, PipeTransform } from '@angular/core';
2 |
3 | @Pipe({
4 | name: 'ellipsis'
5 | })
6 | export class EllipsisPipe implements PipeTransform {
7 |
8 | transform(value: any, args?: any): any {
9 | if (args === undefined) {
10 | return value;
11 | }
12 |
13 | if (value.length > args) {
14 | return value.substring(0, args) + '...';
15 | } else {
16 | return value;
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/app/services/analytics-window.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { ElectronService } from 'ngx-electron';
3 | import { BrowserWindow } from 'electron';
4 | import { InfoDialogService } from './info-dialog.service';
5 |
6 | @Injectable()
7 | export class AnalyticsWindowService {
8 |
9 | constructor(
10 | private electron: ElectronService,
11 | private infoDialog: InfoDialogService
12 | ) { }
13 |
14 | open(apiBase: string, businessId: string, businessName: string) {
15 | if (!this.electron.isElectronApp) {
16 | let url = `/analytics-dashboard/index.html#/?apiBase=${encodeURIComponent(apiBase)}&businessId=${businessId}&businessName=${encodeURIComponent(businessName)}&chatFlowId=${businessId}`;
17 | window.open(url);
18 | return;
19 | }
20 | let url = `file://${this.electron.remote.app.getAppPath()}/analytics-dashboard/index.html#/?apiBase=${encodeURIComponent(apiBase)}&businessId=${businessId}&businessName=${encodeURIComponent(businessName)}&chatFlowId=${businessId}`;
21 |
22 | let win = new this.electron.remote.BrowserWindow({
23 | width: 900,
24 | height: 600,
25 | center: true
26 | });
27 | win.on('closed', () => {
28 | win = null
29 | });
30 | win.loadURL(url);
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/app/services/chatflow.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { Http, RequestOptionsArgs, Headers } from '@angular/http';
3 | import { Observable } from 'rxjs';
4 | import 'rxjs/add/operator/map';
5 | import * as models from '../models/chatflow.models'
6 | import { ChatServerConnection, ChatBotProject } from '../models/app.models';
7 |
8 | @Injectable()
9 | export class ChatFlowService {
10 | constructor(private http: Http) { }
11 |
12 | private publishChatBotAPI: string = "bot/business";
13 |
14 | private normalizeBaseUrl(baseUrl: string) {
15 | baseUrl = baseUrl.replace(/\\$/, '');//Remove ending \ char if any
16 | if (!baseUrl.endsWith('/'))
17 | baseUrl += '/';
18 | return baseUrl;
19 | }
20 |
21 | publishProject(conn: ChatServerConnection, proj: ChatBotProject, pack: models.ChatFlowPack) {
22 | let chatNodes = this.normalizeChatNodes(pack.ChatNodes);
23 |
24 | return this.http.post(this.normalizeBaseUrl(conn.ServerUrl) + this.publishChatBotAPI, {
25 | business_id: proj.Id,
26 | flow: chatNodes,
27 | business_name: proj.Name
28 | }).map(res => res.json());
29 | }
30 |
31 | chatProjectExists(conn: ChatServerConnection, proj: ChatBotProject) {
32 | return this.http.get(this.normalizeBaseUrl(conn.ServerUrl) + this.publishChatBotAPI + "?business_id=" + proj.Id).map(res => res.json());
33 | }
34 |
35 | normalizeChatNodes(chatNodes: models.ChatNode[]) {
36 | chatNodes.forEach(x => {
37 | x.IsStartNode = x.IsStartNode ? true : false //This field should exist even if it's false
38 | });
39 | return chatNodes;
40 | }
41 | }
--------------------------------------------------------------------------------
/src/app/services/globals.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import * as models from '../models/chatflow.models';
3 | import { ANADate, ANATime, AddressInput, GeoLoc } from '../models/ana-chat.models';
4 | import { Title } from '@angular/platform-browser';
5 | import { ChatFlowComponent } from '../components/studio/chatflow/chatflow.component';
6 | import { ObjectID } from 'bson';
7 | import { SectionType, CarouselSection } from '../models/chatflow.models';
8 |
9 | @Injectable()
10 | export class GlobalsService {
11 | constructor(private title: Title) {
12 | }
13 | appName = 'Ana';
14 |
15 | chatFlowComponent: ChatFlowComponent;
16 | loading: boolean = false;
17 |
18 | setPageTitle(title?: string) {
19 | if (title)
20 | this.title.setTitle(`${title} - ${this.appName}`);
21 | else
22 | this.title.setTitle(this.appName);
23 | }
24 |
25 | downloadTextAsFile(filename, text) {
26 | var element = document.createElement('a');
27 | element.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(text));
28 | element.setAttribute('download', filename);
29 |
30 | element.style.display = 'none';
31 | document.body.appendChild(element);
32 |
33 | element.click();
34 |
35 | document.body.removeChild(element);
36 | }
37 |
38 | uuidv4() {
39 | return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).toString().replace(/[018]/g,
40 | c => (c ^ crypto.getRandomValues(new Uint8Array(1))[0] & 15 >> c / 4).toString(16)
41 | )
42 | }
43 | getVariableType(x) {
44 | if (Array.isArray(x)) return VariableType.Array;
45 | else if (typeof x == 'string') return VariableType.String;
46 | else if (x != null && typeof x == 'object') return VariableType.Object;
47 | else return VariableType.Other;
48 | }
49 |
50 | anaDateDisplay(anaDate: ANADate) {
51 | return `${parseInt(anaDate.mday)}-${parseInt(anaDate.month)}-${parseInt(anaDate.year)}`;
52 | }
53 |
54 | anaTimeDisplay(anaTime: ANATime) {
55 | let hr = parseInt(anaTime.hour);
56 | let min = parseInt(anaTime.minute);
57 |
58 | var hours: any = hr > 12 ? hr - 12 : hr;
59 | var am_pm = hr >= 12 ? "PM" : "AM";
60 | hours = hours < 10 ? "0" + hours : hours;
61 | var minutes = min < 10 ? "0" + min : min;
62 |
63 | return hours + ":" + minutes + " " + am_pm;
64 | }
65 |
66 | anaAddressDisplay(anaAddress: AddressInput) {
67 | return [anaAddress.line1, anaAddress.area, anaAddress.city, anaAddress.state, anaAddress.country, anaAddress.pin].filter(x => x).join(", ");
68 | }
69 |
70 | anaLocationDisplay(anaLoc: GeoLoc) {
71 | return `${anaLoc.lat},${anaLoc.lng}`;
72 | }
73 |
74 | EMAIL_REGEX = /^[a-z0-9!#$%&'*+/=?^_`{|}~.-]+@[a-z0-9-]+(\.[a-z0-9-]+)*$/;
75 | emailValid(val: string) {
76 | if (val)
77 | return this.EMAIL_REGEX.test(val);
78 | return false;
79 | }
80 |
81 | PHONE_REGEX = /^\+?\d{6,15}$/;
82 | phoneValid(val: string) {
83 | if (val)
84 | return this.PHONE_REGEX.test(val);
85 | return false;
86 | }
87 |
88 | pwdMatch(p1: string, p2: string) {
89 | if (!p1) return false;
90 | if (p1.length < 6) return false;
91 | return p1 == p2;
92 | }
93 |
94 | normalizeChatNodes(chatNodes: models.ChatNode[]) {
95 | chatNodes.forEach(x => {
96 | x.IsStartNode = x.IsStartNode ? true : false //This field should exist even if it's false
97 | });
98 | return chatNodes;
99 | }
100 |
101 | cloneNode(srcNode: models.ChatNode) {
102 | if (!srcNode) {
103 | return false;
104 | }
105 | let targetNode = JSON.parse(JSON.stringify(srcNode)) as models.ChatNode;
106 | targetNode.Id = new ObjectID().toHexString();
107 | targetNode.Name += " Copy";
108 | targetNode.NextNodeId = null;
109 | if (targetNode.Buttons) {
110 | targetNode.Buttons.forEach(btn => {
111 | btn._id = new ObjectID().toHexString();
112 | btn.NextNodeId = null;
113 | });
114 | }
115 | if (targetNode.Sections) {
116 | targetNode.Sections.forEach(section => {
117 | section._id = new ObjectID().toHexString();
118 | if (section.SectionType == SectionType.Carousel) {
119 | let car = section as CarouselSection;
120 | car.Items.forEach(carItem => {
121 | carItem._id = new ObjectID().toHexString();
122 | carItem.Buttons.forEach(carBtn => {
123 | carBtn._id = new ObjectID().toHexString();
124 | carBtn.NextNodeId = null;
125 | });
126 | });
127 | }
128 | });
129 | }
130 | return targetNode;
131 | }
132 | }
133 |
134 | export enum VariableType {
135 | Array, String, Object, Other
136 | }
137 |
--------------------------------------------------------------------------------
/src/app/services/info-dialog.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { MatDialog, MatDialogRef } from '@angular/material';
3 | import { InfoDialogComponent, InfoDialogOptions, InfoDialogType } from '../components/shared/info-dialog/info-dialog.component';
4 | import { LoadingMaskComponent } from '../components/shared/loading-mask/loading-mask.component';
5 |
6 | @Injectable()
7 | export class InfoDialogService {
8 | constructor(private dialog: MatDialog) { }
9 |
10 | alert(title: string, message: string, callback?: () => void) {
11 | let data: InfoDialogOptions = {
12 | dialogType: InfoDialogType.Alert,
13 | message: message,
14 | title: title
15 | }
16 | let d = this.dialog.open(InfoDialogComponent, {
17 | width: 'auto',
18 | data: data
19 | });
20 | if (callback)
21 | d.afterClosed().subscribe(x => {
22 | if (callback)
23 | callback();
24 | });
25 | }
26 | prompt(title: string, message: string, callback: (result: string) => void, defaultText?: string) {
27 | let data: InfoDialogOptions = {
28 | dialogType: InfoDialogType.Prompt,
29 | message: message,
30 | title: title,
31 | defaultInputText: defaultText
32 | }
33 |
34 | let d = this.dialog.open(InfoDialogComponent, {
35 | width: 'auto',
36 | data: data
37 | });
38 | d.afterClosed().subscribe(x => {
39 | callback(x as string);
40 | });
41 | }
42 | confirm(title: string, message: string, callback: (result: boolean) => void) {
43 | let data: InfoDialogOptions = {
44 | dialogType: InfoDialogType.Confirm,
45 | message: message,
46 | title: title
47 | }
48 |
49 | let d = this.dialog.open(InfoDialogComponent, {
50 | width: 'auto',
51 | data: data
52 | });
53 | d.afterClosed().subscribe(x => {
54 | callback(x as boolean);
55 | });
56 | }
57 |
58 | dialogRef: MatDialogRef;
59 | showSpinner() {
60 | this.hideSpinner();
61 | this.dialogRef = this.dialog.open(LoadingMaskComponent, {
62 | width: 'auto',
63 | disableClose: true,
64 | panelClass: 'trans-background'
65 | });
66 | }
67 |
68 | hideSpinner() {
69 | if (this.dialogRef) {
70 | this.dialogRef.close();
71 | delete this.dialogRef;
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/app/services/login.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { DataService } from './data.service';
3 | import { MatDialog } from '@angular/material';
4 | import { LoginComponent } from '../components/shared/login/login.component';
5 | import { Router } from '@angular/router';
6 |
7 | @Injectable()
8 | export class LoginService {
9 | constructor(
10 | private dataService: DataService,
11 | private dialog: MatDialog,
12 | private router: Router) {
13 | }
14 |
15 | performLogin(skipAuth: boolean, fallbackUrl: string = "/", hardCheck: boolean = false, next?: (done: boolean) => void) {
16 | this.dataService.userLoggedinCheck((loggedin) => {
17 | if (!loggedin && skipAuth == false) {
18 | let d = this.dialog.open(LoginComponent, {
19 | width: '600px',
20 | });
21 |
22 | d.afterClosed().subscribe(x => {
23 | if (x == true) {
24 | if (next)
25 | next(true);
26 | return;
27 | }
28 | if (fallbackUrl)
29 | this.router.navigateByUrl(fallbackUrl);
30 | if (next)
31 | next(true);
32 | });
33 | } else {
34 | if (next)
35 | next(loggedin);
36 | }
37 | }, hardCheck);
38 | }
39 |
40 | }
--------------------------------------------------------------------------------
/src/app/services/settings.service.ts:
--------------------------------------------------------------------------------
1 | import { Injectable } from '@angular/core';
2 | import { ChatServerConnection } from '../models/app.models'
3 | import { ChatFlowPack } from '../models/chatflow.models';
4 | import { InfoDialogService } from '../services/info-dialog.service';
5 | @Injectable()
6 | export class SettingsService {
7 | constructor(private infoDialog: InfoDialogService) { }
8 | loadSavedConnections() {
9 | var loaded = JSON.parse(localStorage.getItem(SettingKey.SavedConnsKey)) as ChatServerConnection[];
10 | if (loaded)
11 | return loaded;
12 | else
13 | return [];
14 | }
15 |
16 | saveSavedConnections(connections: ChatServerConnection[]) {
17 | localStorage.setItem(SettingKey.SavedConnsKey, JSON.stringify(connections));
18 | }
19 |
20 | saveChatProject(chatProjectName: string, pack: ChatFlowPack, quite: boolean, next?: () => void) {
21 | chatProjectName += '.anaproj';
22 |
23 | if (quite) {
24 | localStorage.setItem(chatProjectName, JSON.stringify(pack));
25 | if (next) next();
26 | } else {
27 | let existing = localStorage.getItem(chatProjectName);
28 | if (existing) {
29 | this.infoDialog.confirm('Sure?', 'Chat project the given name already exists. Do you want to overwrite it?', (ok) => {
30 | if (ok) {
31 | localStorage.setItem(chatProjectName, JSON.stringify(pack));
32 | if (next) next();
33 | }
34 | });
35 | }
36 | else {
37 | localStorage.setItem(chatProjectName, JSON.stringify(pack));
38 | if (next) next();
39 | }
40 | }
41 | }
42 |
43 | getChatProject(chatProjectName: string) {
44 | chatProjectName += '.anaproj';
45 |
46 | let existing = localStorage.getItem(chatProjectName);
47 | if (!existing) {
48 | this.infoDialog.alert('Not found', `Chat Project with name '${chatProjectName}' does not exist`);
49 | return null;
50 | }
51 | return JSON.parse(existing) as ChatFlowPack;
52 | }
53 |
54 | listSavedChatProjectNames() {
55 | let savedProjs = [];
56 | for (var key in localStorage) {
57 | if (key.endsWith('.anaproj')) {
58 | let name = key.replace(new RegExp('\.anaproj$'), '');
59 | savedProjs.push(name);
60 | }
61 | }
62 | return savedProjs.sort((x, y) => ((x && y) ? x.localeCompare(y) : 1));
63 | }
64 |
65 | renameChatProject(oldName: string, newName: string) {
66 | if (oldName == newName)
67 | return;
68 |
69 | oldName += ".anaproj";
70 | newName += ".anaproj";
71 |
72 | let temp = localStorage.getItem(oldName);
73 | if (!temp) {
74 | this.infoDialog.alert('Not found', `${oldName} not found`);
75 | return;
76 | }
77 | localStorage.setItem(newName, temp);
78 | localStorage.removeItem(oldName);
79 | }
80 |
81 | deleteChatProject(name: string) {
82 | name += ".anaproj";
83 |
84 | let exists = localStorage.getItem(name);
85 | if (!exists) {
86 | this.infoDialog.alert('Not found', `${name} not found`);
87 | return false;
88 | }
89 | localStorage.removeItem(name);
90 | return true;
91 | }
92 | }
93 |
94 | enum SettingKey {
95 | SavedConnsKey = 'SAVED_CONNECTIONS'
96 | }
97 |
--------------------------------------------------------------------------------
/src/assets/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nowfloats/ProjectAna/def5c4c9148fd72e59fe235eb34a3cd9a6a6aff3/src/assets/.gitkeep
--------------------------------------------------------------------------------
/src/assets/fonts/Material_Icons_400_normal.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nowfloats/ProjectAna/def5c4c9148fd72e59fe235eb34a3cd9a6a6aff3/src/assets/fonts/Material_Icons_400_normal.woff
--------------------------------------------------------------------------------
/src/assets/fonts/Open_Sans_400_normal.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nowfloats/ProjectAna/def5c4c9148fd72e59fe235eb34a3cd9a6a6aff3/src/assets/fonts/Open_Sans_400_normal.ttf
--------------------------------------------------------------------------------
/src/assets/fonts/Open_Sans_400_normal.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nowfloats/ProjectAna/def5c4c9148fd72e59fe235eb34a3cd9a6a6aff3/src/assets/fonts/Open_Sans_400_normal.woff
--------------------------------------------------------------------------------
/src/assets/fonts/material-icons-fonts.css:
--------------------------------------------------------------------------------
1 | /* fallback */
2 | @font-face {
3 | font-family: 'Material Icons';
4 | font-style: normal;
5 | font-weight: 400;
6 | src: url(Material_Icons_400_normal.woff) format('woff2');
7 | }
8 |
9 | .material-icons {
10 | font-family: 'Material Icons';
11 | font-weight: normal;
12 | font-style: normal;
13 | font-size: 24px;
14 | line-height: 1;
15 | letter-spacing: normal;
16 | text-transform: none;
17 | display: inline-block;
18 | white-space: nowrap;
19 | word-wrap: normal;
20 | direction: ltr;
21 | -webkit-font-feature-settings: 'liga';
22 | -webkit-font-smoothing: antialiased;
23 | }
24 |
--------------------------------------------------------------------------------
/src/assets/fonts/open-sans-font.css:
--------------------------------------------------------------------------------
1 | @font-face {
2 | font-family: 'Open Sans';
3 | font-style: normal;
4 | font-weight: 400;
5 | src: url(Open_Sans_400_normal.eot); /* {{embedded-opentype-gf-url}} */
6 | src: local('☺'), url(Open_Sans_400_normal.eot?#iefix) format('embedded-opentype'), /* {{embedded-opentype-gf-url}} */
7 | url(Open_Sans_400_normal.woff) format('woff'), /* http://fonts.gstatic.com/s/opensans/v15/cJZKeOuBrn4kERxqtaUH3T8E0i7KZn-EPnyo3HZu7kw.woff */
8 | url(Open_Sans_400_normal.ttf) format('truetype'), /* http://fonts.gstatic.com/s/opensans/v15/cJZKeOuBrn4kERxqtaUH3SZ2oysoEQEeKwjgmXLRnTc.ttf */
9 | url(Open_Sans_400_normal.svg#Open+Sans_400_normal) format('svg'); /* http://fonts.gstatic.com/l/font?kit=cJZKeOuBrn4kERxqtaUH3Zbd9NUM7myrQQz30yPaGQ4&skey=62c1cbfccc78b4b2&v=v15#OpenSans */
10 | }
11 |
--------------------------------------------------------------------------------
/src/assets/img/ana.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/img/loading.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/assets/img/node-editor/carousel-hover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nowfloats/ProjectAna/def5c4c9148fd72e59fe235eb34a3cd9a6a6aff3/src/assets/img/node-editor/carousel-hover.png
--------------------------------------------------------------------------------
/src/assets/img/node-editor/carousel.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nowfloats/ProjectAna/def5c4c9148fd72e59fe235eb34a3cd9a6a6aff3/src/assets/img/node-editor/carousel.png
--------------------------------------------------------------------------------
/src/assets/img/node-editor/gif-hover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nowfloats/ProjectAna/def5c4c9148fd72e59fe235eb34a3cd9a6a6aff3/src/assets/img/node-editor/gif-hover.png
--------------------------------------------------------------------------------
/src/assets/img/node-editor/gif.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nowfloats/ProjectAna/def5c4c9148fd72e59fe235eb34a3cd9a6a6aff3/src/assets/img/node-editor/gif.png
--------------------------------------------------------------------------------
/src/assets/img/node-editor/image-hover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nowfloats/ProjectAna/def5c4c9148fd72e59fe235eb34a3cd9a6a6aff3/src/assets/img/node-editor/image-hover.png
--------------------------------------------------------------------------------
/src/assets/img/node-editor/image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nowfloats/ProjectAna/def5c4c9148fd72e59fe235eb34a3cd9a6a6aff3/src/assets/img/node-editor/image.png
--------------------------------------------------------------------------------
/src/assets/img/node-editor/text-hover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nowfloats/ProjectAna/def5c4c9148fd72e59fe235eb34a3cd9a6a6aff3/src/assets/img/node-editor/text-hover.png
--------------------------------------------------------------------------------
/src/assets/img/node-editor/text.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nowfloats/ProjectAna/def5c4c9148fd72e59fe235eb34a3cd9a6a6aff3/src/assets/img/node-editor/text.png
--------------------------------------------------------------------------------
/src/assets/img/node-editor/video-hover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nowfloats/ProjectAna/def5c4c9148fd72e59fe235eb34a3cd9a6a6aff3/src/assets/img/node-editor/video-hover.png
--------------------------------------------------------------------------------
/src/assets/img/node-editor/video.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nowfloats/ProjectAna/def5c4c9148fd72e59fe235eb34a3cd9a6a6aff3/src/assets/img/node-editor/video.png
--------------------------------------------------------------------------------
/src/electron.js:
--------------------------------------------------------------------------------
1 | const { app, BrowserWindow, Menu, dialog, shell } = require('electron');
2 | const path = require('path');
3 | const url = require('url');
4 | const appVersion = require('./package.json').version;
5 | const { autoUpdater } = require('electron-updater');
6 |
7 | let win;
8 |
9 | function createWindow() {
10 | win = new BrowserWindow({ width: 900, height: 600, show: false });
11 | if (process.platform === 'darwin') {
12 | autoUpdater.on('update-available', (info) => {
13 | dialog.showMessageBox(win, {
14 | type: 'question',
15 | buttons: ['Yes', 'No'],
16 | message: `Update ${info.version} Available!\nWould you like to download it?`
17 | }, (response) => {
18 | if (response === 0)
19 | shell.openExternal(`https://cdn.ana.chat/dist/ana-app/mac-x64/Ana-${info.version}.dmg`);
20 | });
21 | });
22 | }
23 | autoUpdater.checkForUpdatesAndNotify();
24 |
25 | splash = new BrowserWindow({ width: 810, height: 610, transparent: true, frame: false, alwaysOnTop: true });
26 | splash.loadURL(`file://${__dirname}/splash.html`);
27 |
28 | win.loadURL(url.format({
29 | pathname: path.join(__dirname, 'index.html'),
30 | protocol: 'file:',
31 | slashes: true
32 | }));
33 |
34 | win.once('ready-to-show', () => {
35 | splash.destroy();
36 | win.show();
37 | });
38 |
39 | win.on('closed', () => {
40 | win = null
41 | });
42 |
43 | const template = [
44 | {
45 | label: 'Edit',
46 | submenu: [
47 | { role: 'undo' },
48 | { role: 'redo' },
49 | { type: 'separator' },
50 | { role: 'cut' },
51 | { role: 'copy' },
52 | { role: 'paste' },
53 | { role: 'pasteandmatchstyle' },
54 | { role: 'delete' },
55 | { role: 'selectall' }
56 | ]
57 | },
58 | {
59 | label: 'View',
60 | submenu: [
61 | { role: 'resetzoom' },
62 | { role: 'zoomin' },
63 | { role: 'zoomout' },
64 | { type: 'separator' },
65 | { role: 'togglefullscreen' },
66 | { role: 'toggledevtools' }
67 | ]
68 | },
69 | {
70 | role: 'window',
71 | submenu: [
72 | { role: 'minimize' },
73 | { role: 'close' }
74 | ]
75 | },
76 | {
77 | role: 'help',
78 | submenu: [
79 | {
80 | label: 'About Ana',
81 | click() { shell.openExternal('http://ana.chat') }
82 | },
83 | {
84 | label: 'Version ' + appVersion,
85 | enabled: false
86 | }
87 | ]
88 | }
89 | ]
90 |
91 | if (process.platform === 'darwin') {
92 | template.unshift({
93 | label: app.getName(),
94 | submenu: [
95 | { role: 'about' },
96 | { type: 'separator' },
97 | { role: 'services', submenu: [] },
98 | { type: 'separator' },
99 | { role: 'hide' },
100 | { role: 'hideothers' },
101 | { role: 'unhide' },
102 | { type: 'separator' },
103 | { role: 'quit' }
104 | ]
105 | })
106 |
107 | // Edit menu
108 | template[1].submenu.push(
109 | { type: 'separator' },
110 | {
111 | label: 'Speech',
112 | submenu: [
113 | { role: 'startspeaking' },
114 | { role: 'stopspeaking' }
115 | ]
116 | }
117 | )
118 |
119 | // Window menu
120 | template[3].submenu = [
121 | { role: 'minimize' },
122 | { role: 'zoom' },
123 | { type: 'separator' },
124 | { role: 'front' }
125 | ]
126 | } else {
127 | template.unshift({
128 | label: 'File',
129 | submenu: [
130 | { role: 'quit' }
131 | ]
132 | })
133 | }
134 |
135 | const menu = Menu.buildFromTemplate(template)
136 | Menu.setApplicationMenu(menu)
137 | }
138 | app.on('ready', createWindow)
139 | app.on('window-all-closed', () => {
140 | if (process.platform !== 'darwin')
141 | app.quit()
142 | })
143 |
144 | app.on('activate', () => {
145 | if (win === null)
146 | createWindow()
147 | })
148 |
--------------------------------------------------------------------------------
/src/environments/environment.local.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: false,
3 | local: true
4 | };
5 |
--------------------------------------------------------------------------------
/src/environments/environment.prod.ts:
--------------------------------------------------------------------------------
1 | export const environment = {
2 | production: true,
3 | local: false
4 | };
5 |
--------------------------------------------------------------------------------
/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 | local: false
9 | };
10 |
--------------------------------------------------------------------------------
/src/favicon.icns:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nowfloats/ProjectAna/def5c4c9148fd72e59fe235eb34a3cd9a6a6aff3/src/favicon.icns
--------------------------------------------------------------------------------
/src/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nowfloats/ProjectAna/def5c4c9148fd72e59fe235eb34a3cd9a6a6aff3/src/favicon.ico
--------------------------------------------------------------------------------
/src/favicon/256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/nowfloats/ProjectAna/def5c4c9148fd72e59fe235eb34a3cd9a6a6aff3/src/favicon/256x256.png
--------------------------------------------------------------------------------
/src/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Ana
6 |
7 |
8 |
9 |
10 |
11 |
12 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
--------------------------------------------------------------------------------
/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 | if (environment.production) {
8 | enableProdMode();
9 | }
10 |
11 | platformBrowserDynamic().bootstrapModule(AppModule);
12 |
--------------------------------------------------------------------------------
/src/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "ana-conversation-studio",
3 | "version": "0.1.8",
4 | "main": "electron.js",
5 | "author": "Team Ana",
6 | "description": "The Ana app, contains Ana studio, user management, analytics and chatbot publishing.",
7 | "homepage": "http://ana.chat",
8 | "license": "GNU-GPLv3",
9 | "build": {
10 | "appId": "com.ana.studio",
11 | "productName": "Ana",
12 | "directories": {
13 | "output": "../release",
14 | "app": ".",
15 | "buildResources": "../build"
16 | },
17 | "mac": {
18 | "icon": "favicon.icns",
19 | "category": "public.app-category.graphics-design",
20 | "target": [
21 | {
22 | "target": "dmg",
23 | "arch": [
24 | "x64"
25 | ]
26 | }
27 | ]
28 | },
29 | "win": {
30 | "icon": "favicon.ico",
31 | "target": [
32 | {
33 | "target": "nsis",
34 | "arch": [
35 | "x64",
36 | "ia32"
37 | ]
38 | }
39 | ]
40 | },
41 | "linux": {
42 | "category": "Graphics",
43 | "icon": "favicon",
44 | "target": [
45 | {
46 | "target": "AppImage",
47 | "arch": [
48 | "x64"
49 | ]
50 | }
51 | ]
52 | },
53 | "electronVersion": "1.8.2",
54 | "publish": {
55 | "provider": "generic",
56 | "url": "https://cdn.ana.chat/dist/ana-app/${os}-${arch}"
57 | }
58 | },
59 | "dependencies": {
60 | "electron-updater": "^2.20.1"
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/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 | /** Evergreen browsers require these. **/
41 | import 'core-js/es6/reflect';
42 | import 'core-js/es7/reflect';
43 |
44 |
45 | /**
46 | * Required to support Web Animations `@angular/animation`.
47 | * Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation
48 | **/
49 | //import 'web-animations-js'; // Run `npm install --save web-animations-js`.
50 |
51 |
52 |
53 | /***************************************************************************************************
54 | * Zone JS is required by Angular itself.
55 | */
56 | import 'zone.js/dist/zone'; // Included with Angular CLI.
57 |
58 |
59 |
60 | /***************************************************************************************************
61 | * APPLICATION IMPORTS
62 | */
63 |
64 | /**
65 | * Date, currency, decimal and percent pipes.
66 | * Needed for: All but Chrome, Firefox, Edge, IE11 and Safari 10
67 | */
68 | // import 'intl'; // Run `npm install --save intl`.
69 | /**
70 | * Need to import at least one locale-data with intl.
71 | */
72 | // import 'intl/locale-data/jsonp/en';
73 |
--------------------------------------------------------------------------------
/src/splash.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Ana
6 |
7 |
33 |
34 |
35 |
36 |
37 |

38 |
39 |
61 |
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/src/styles.css:
--------------------------------------------------------------------------------
1 | /* You can add global styles to this file, and also import other style files */
2 | @import '../node_modules/bootstrap/dist/css/bootstrap.min.css';
3 | @import '../node_modules/font-awesome/css/font-awesome.min.css';
4 |
5 | body {
6 | font-family: 'Open Sans';
7 | background-color: #f5f5f5;
8 | }
9 |
10 | [hidden] {
11 | display: none !important;
12 | }
13 |
14 | .fab-btn-icon {
15 | color: white;
16 | }
17 |
18 | .panel-heading-actions {
19 | margin: -10px;
20 | }
21 |
22 | .dialog-root {
23 | margin: 20px 0;
24 | height: 90vh;
25 | }
26 |
27 | .root {
28 | margin: 20px 0;
29 | height: 90vh;
30 | width: 98vw;
31 | }
32 |
33 | .root > .center-content {
34 | width: 800px;
35 | }
36 |
37 | input[type=text] {
38 | line-height: normal;
39 | }
40 |
41 | .danger-button {
42 | background-color: crimson !important;
43 | color: white !important;
44 | }
45 |
46 | .white-button {
47 | background-color: white !important;
48 | color: crimson !important;
49 | }
50 |
51 | .white-button .fab-btn-icon {
52 | color: crimson !important;
53 | }
54 |
55 | .mat-dialog-content {
56 | padding-top: 10px !important;
57 | padding-bottom: 10px !important;
58 | }
59 |
60 | .mat-select-placeholder {
61 | font-size: 14px;
62 | font-weight: 400;
63 | line-height: 1.125;
64 | }
65 |
66 | .mat-select {
67 | font-family: 'Open Sans' !important;
68 | }
69 |
70 | .mat-hint {
71 | margin: 0 0 4px 0 !important;
72 | }
73 |
74 | .mat-form-field.contains-hint {
75 | padding-bottom: 10px;
76 | }
77 |
78 | .mat-dialog-title {
79 | margin: 0 !important;
80 | }
81 |
82 | .mat-progress-bar-buffer {
83 | background-color: white;
84 | }
85 |
86 | .mat-raised-button.mat-primary {
87 | color: white;
88 | }
89 |
90 | .mat-raised-button.mat-primary.inverse {
91 | background-color: white;
92 | color: #8cc83c;
93 | }
94 |
95 | /*Dynamic styles*/
96 | .chat-message-item.incoming {
97 | border-left-color: #8cc83c;
98 | }
99 |
100 | .incoming > .chat-stub {
101 | border-top-color: #8cc83c;
102 | }
103 |
104 | .carousel-item-button:first-child,
105 | .chat-input button.btn-icon {
106 | color: #8cc83c;
107 | }
108 |
109 | .chat-input button.btn-click {
110 | background-color: #8cc83c;
111 | color: white;
112 | }
113 |
114 | .chat-message-item.outgoing {
115 | border-right-color: black;
116 | }
117 |
118 | .outgoing > .chat-stub {
119 | border-top-color: black;
120 | }
121 |
122 | .complex-input-btn-done {
123 | color: white !important;
124 | }
125 |
126 | .content {
127 | width: 100vw;
128 | }
129 |
130 | .ana-title {
131 | background-color: #8cc83c;
132 | color: white;
133 | }
134 |
135 | .ana-logo > img {
136 | background-color: white;
137 | border: 2px solid white;
138 | }
139 |
140 | .ana-min .ana-minmax-btn {
141 | border: 2px solid white;
142 | }
143 |
144 | .ana-minmax-btn {
145 | background-color: white;
146 | }
147 |
148 | .typing-indicator span {
149 | background-color: #8cc83c;
150 | }
151 |
152 | .mat-toolbar.mat-primary {
153 | color: white;
154 | }
155 |
156 | .trans-background .mat-dialog-container {
157 | -moz-box-shadow: none;
158 | -webkit-box-shadow: none;
159 | box-shadow: none;
160 | background-color: rgba(0,0,0,0.15);
161 | -moz-border-radius: 100%;
162 | -webkit-border-radius: 100%;
163 | border-radius: 100%;
164 | }
165 |
166 | .dark-overlay {
167 | background-color: black;
168 | opacity: 0.8 !important;
169 | }
170 |
171 | app-nodeeditor .mat-tab-body-wrapper {
172 | height: 50vh;
173 | }
174 |
175 | .mat-radio-label-content {
176 | font-weight: normal;
177 | }
--------------------------------------------------------------------------------
/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/long-stack-trace-zone';
4 | import 'zone.js/dist/proxy.js';
5 | import 'zone.js/dist/sync-test';
6 | import 'zone.js/dist/jasmine-patch';
7 | import 'zone.js/dist/async-test';
8 | import 'zone.js/dist/fake-async-test';
9 | import { getTestBed } from '@angular/core/testing';
10 | import {
11 | BrowserDynamicTestingModule,
12 | platformBrowserDynamicTesting
13 | } from '@angular/platform-browser-dynamic/testing';
14 |
15 | // Unfortunately there's no typing for the `__karma__` variable. Just declare it as any.
16 | declare const __karma__: any;
17 | declare const require: any;
18 |
19 | // Prevent Karma from running prematurely.
20 | __karma__.loaded = function () {};
21 |
22 | // First, initialize the Angular testing environment.
23 | getTestBed().initTestEnvironment(
24 | BrowserDynamicTestingModule,
25 | platformBrowserDynamicTesting()
26 | );
27 | // Then we find all the tests.
28 | const context = require.context('./', true, /\.spec\.ts$/);
29 | // And load the modules.
30 | context.keys().map(context);
31 | // Finally, start Karma to run the tests.
32 | __karma__.start();
33 |
--------------------------------------------------------------------------------
/src/theme.scss:
--------------------------------------------------------------------------------
1 | @import '~@angular/material/theming';
2 | @include mat-core();
3 | $mat-ana-green: (
4 | 50: #8cc83c,
5 | 100: #8cc83c,
6 | 200: #8cc83c,
7 | 300: #8cc83c,
8 | 400: #8cc83c,
9 | 500: #8cc83c,
10 | 600: #8cc83c,
11 | 700: #8cc83c,
12 | 800: #8cc83c,
13 | 900: #8cc83c,
14 | A100: #8cc83c,
15 | A200: #8cc83c,
16 | A400: #8cc83c,
17 | A700: #8cc83c,
18 | contrast: (
19 | 50: $black-87-opacity,
20 | 100: $black-87-opacity,
21 | 200: $black-87-opacity,
22 | 300: white,
23 | 400: white,
24 | 500: $white-87-opacity,
25 | 600: $white-87-opacity,
26 | 700: $white-87-opacity,
27 | 800: $white-87-opacity,
28 | 900: $white-87-opacity,
29 | A100: $black-87-opacity,
30 | A200: white,
31 | A400: white,
32 | A700: $white-87-opacity,
33 | )
34 | );
35 |
36 | $ana-app-primary: mat-palette($mat-ana-green);
37 | $ana-app-accent: mat-palette($mat-ana-green, A200, A100, A400);
38 | $ana-app-warn: mat-palette($mat-red);
39 | $ana-app-theme: mat-light-theme($ana-app-primary, $ana-app-accent, $ana-app-warn);
40 | @include angular-material-theme($ana-app-theme);
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/tsconfig.spec.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "../tsconfig.json",
3 | "compilerOptions": {
4 | "outDir": "../out-tsc/spec",
5 | "baseUrl": "./",
6 | "module": "commonjs",
7 | "target": "es5",
8 | "types": [
9 | "jasmine",
10 | "node"
11 | ]
12 | },
13 | "files": [
14 | "test.ts"
15 | ],
16 | "include": [
17 | "**/*.spec.ts",
18 | "**/*.d.ts"
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/src/typings.d.ts:
--------------------------------------------------------------------------------
1 | /* SystemJS module definition */
2 | declare var module: NodeModule;
3 | interface NodeModule {
4 | id: string;
5 | }
6 |
--------------------------------------------------------------------------------
/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 | "exclude": [
20 | "src/node_modules"
21 | ]
22 | }
23 |
--------------------------------------------------------------------------------
/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 | "eofline": true,
15 | "forin": true,
16 | "import-blacklist": [
17 | true,
18 | "rxjs"
19 | ],
20 | "import-spacing": true,
21 | "indent": [
22 | true,
23 | "spaces"
24 | ],
25 | "interface-over-type-literal": true,
26 | "label-position": true,
27 | "max-line-length": [
28 | true,
29 | 140
30 | ],
31 | "member-access": false,
32 | "member-ordering": [
33 | true,
34 | {
35 | "order": [
36 | "static-field",
37 | "instance-field",
38 | "static-method",
39 | "instance-method"
40 | ]
41 | }
42 | ],
43 | "no-arg": true,
44 | "no-bitwise": true,
45 | "no-console": [
46 | true,
47 | "debug",
48 | "info",
49 | "time",
50 | "timeEnd",
51 | "trace"
52 | ],
53 | "no-construct": true,
54 | "no-debugger": true,
55 | "no-duplicate-super": true,
56 | "no-empty": false,
57 | "no-empty-interface": true,
58 | "no-eval": true,
59 | "no-inferrable-types": [
60 | true,
61 | "ignore-params"
62 | ],
63 | "no-misused-new": true,
64 | "no-non-null-assertion": true,
65 | "no-shadowed-variable": true,
66 | "no-string-literal": false,
67 | "no-string-throw": true,
68 | "no-switch-case-fall-through": true,
69 | "no-trailing-whitespace": true,
70 | "no-unnecessary-initializer": true,
71 | "no-unused-expression": true,
72 | "no-use-before-declare": true,
73 | "no-var-keyword": true,
74 | "object-literal-sort-keys": false,
75 | "one-line": [
76 | true,
77 | "check-open-brace",
78 | "check-catch",
79 | "check-else",
80 | "check-whitespace"
81 | ],
82 | "prefer-const": true,
83 | "quotemark": [
84 | true,
85 | "single"
86 | ],
87 | "radix": true,
88 | "semicolon": [
89 | true,
90 | "always"
91 | ],
92 | "triple-equals": [
93 | true,
94 | "allow-null-check"
95 | ],
96 | "typedef-whitespace": [
97 | true,
98 | {
99 | "call-signature": "nospace",
100 | "index-signature": "nospace",
101 | "parameter": "nospace",
102 | "property-declaration": "nospace",
103 | "variable-declaration": "nospace"
104 | }
105 | ],
106 | "typeof-compare": true,
107 | "unified-signatures": true,
108 | "variable-name": false,
109 | "whitespace": [
110 | true,
111 | "check-branch",
112 | "check-decl",
113 | "check-operator",
114 | "check-separator",
115 | "check-type"
116 | ],
117 | "directive-selector": [
118 | true,
119 | "attribute",
120 | "app",
121 | "camelCase"
122 | ],
123 | "component-selector": [
124 | true,
125 | "element",
126 | "app",
127 | "kebab-case"
128 | ],
129 | "use-input-property-decorator": true,
130 | "use-output-property-decorator": true,
131 | "use-host-property-decorator": true,
132 | "no-input-rename": true,
133 | "no-output-rename": true,
134 | "use-life-cycle-interface": true,
135 | "use-pipe-transform-interface": true,
136 | "component-class-suffix": true,
137 | "directive-class-suffix": true,
138 | "no-access-missing-member": true,
139 | "templates-use-public": true,
140 | "invoke-injectable": true
141 | }
142 | }
143 |
--------------------------------------------------------------------------------