1
1
mirror of https://github.com/leon-ai/leon.git synced 2024-10-03 20:57:55 +03:00

chore: init

This commit is contained in:
Louistiti 2019-02-10 20:26:50 +08:00
commit ed093bf654
207 changed files with 23098 additions and 0 deletions

23
.babelrc Normal file
View File

@ -0,0 +1,23 @@
{
"presets": [
["env", {
"targets": {
"node": "current"
}
}]
],
"plugins": [
["module-resolver", {
"alias": {
"@@": ".",
"@": "./server/src"
},
"compilerOptions": {
"paths": {
"@@": ".",
"@": "./server/src"
}
}
}]
]
}

25
.changelogrc Normal file
View File

@ -0,0 +1,25 @@
{
"app_name": "Leon",
"sections": [
{
"title": "BREAKING CHANGES",
"grep": "BREAKING"
},
{
"title": "Features",
"grep": "^feat"
},
{
"title": "Bug Fixes",
"grep": "^fix"
},
{
"title": "Performance Improvements",
"grep": "^perf"
},
{
"title": "Documentation Changes",
"grep": "^docs"
}
]
}

151
.circleci/config.yml Normal file
View File

@ -0,0 +1,151 @@
version: 2
defaults: &defaults
working_directory: ~/project/leon-ai/leon
docker:
- image: leonai/ci:latest
environment:
LC_ALL: C.UTF-8 # Fix https://click.palletsprojects.com/en/7.x/python3/#python-3-surrogate-handling
LANG: C.UTF-8 # Fix https://click.palletsprojects.com/en/7.x/python3/#python-3-surrogate-handling
PIPENV_PIPFILE: bridges/python/Pipfile
jobs:
check-stack:
<<: *defaults
steps:
- checkout
- run:
name: Checking Node.js
command: node --version
- run:
name: Checking npm
command: npm --version
- run:
name: Checking Python
command: python3 --version
- run:
name: Checking Pipenv
command: pipenv --version
- persist_to_workspace:
root: ~/project/leon-ai
paths:
- leon
install-npm-dep:
<<: *defaults
steps:
- attach_workspace:
at: ~/project/leon-ai
- restore_cache:
keys:
- v1-leon-ai-leon-{{ .Branch }}-{{ checksum "package-lock.json" }}
- v1-leon-ai-leon-{{ .Branch }}-
- v1-leon-ai-leon-
- run:
name: Installing npm dependencies
command: npm install
- save_cache:
key: v1-leon-ai-leon-{{ .Branch }}-{{ checksum "package-lock.json" }}
paths:
- node_modules
- persist_to_workspace:
root: ~/project/leon-ai
paths:
- leon
init:
<<: *defaults
steps:
- attach_workspace:
at: ~/project/leon-ai
- restore_cache:
keys:
- v1-leon-ai-leon-{{ .Branch }}-{{ checksum "bridges/python/Pipfile.lock" }}
- v1-leon-ai-leon-{{ .Branch }}-
- v1-leon-ai-leon-
- run:
name: Setting up
command: npm run postinstall
- save_cache:
key: v1-leon-ai-leon-{{ .Branch }}-{{ checksum "bridges/python/Pipfile.lock" }}
paths:
- bridges/python/.venv
- persist_to_workspace:
root: ~/project/leon-ai
paths:
- leon
lint:
<<: *defaults
steps:
- attach_workspace:
at: ~/project/leon-ai
- run:
name: Linting
command: npm run lint
test-json:
<<: *defaults
steps:
- attach_workspace:
at: ~/project/leon-ai
- run:
name: JSON testing
command: npm run test:json
test-e2e:
<<: *defaults
steps:
- attach_workspace:
at: ~/project/leon-ai
- run:
name: End-to-end testing
command: npm run test:e2e
test-unit:
<<: *defaults
steps:
- attach_workspace:
at: ~/project/leon-ai
- run:
name: Installing offline STT
command: npm run setup:offline-stt
- run:
name: Installing offline TTS
command: npm run setup:offline-tts
- run:
name: Unit testing
command: npm run test:unit
workflows:
version: 2
install-test:
jobs:
- check-stack
- install-npm-dep:
requires:
- check-stack
- init:
requires:
- check-stack
- install-npm-dep
- lint:
requires:
- check-stack
- install-npm-dep
- init
- test-json:
requires:
- check-stack
- install-npm-dep
- init
- test-e2e:
requires:
- check-stack
- install-npm-dep
- init
- test-unit:
requires:
- check-stack
- install-npm-dep
- init

12
.editorconfig Normal file
View File

@ -0,0 +1,12 @@
# EditorConfig is awesome: http://EditorConfig.org
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
indent_style = space
indent_size = 2
charset = utf-8
end_of_line = lf
insert_final_newline = true

42
.env.sample Normal file
View File

@ -0,0 +1,42 @@
# Language currently used
LEON_LANG=en-US
# Current env
LEON_NODE_ENV=development
# Server
LEON_SERVER_HOST=localhost
LEON_SERVER_PORT=1337
# Web app
LEON_WEBAPP_HOST=localhost
LEON_WEBAPP_PORT=4242
# Time zone (current one by default)
LEON_TIME_ZONE=
# Enable/disable after speech
LEON_AFTER_SPEECH=false
# Enable/disable Leon's speech-to-text
LEON_STT=false
# Speech-to-text provider
LEON_STT_PROVIDER=deepspeech
# Enable/disable Leon's text-to-speech
LEON_TTS=false
# Text-to-speech provider
LEON_TTS_PROVIDER=flite
# Enable/disable collaborative logger
LEON_LOGGER=true
# Path to the Pipfile
PIPENV_PIPFILE=bridges/python/Pipfile
# Path to the virtual env in .venv/
PIPENV_VENV_IN_PROJECT=true
# Fix https://click.palletsprojects.com/en/7.x/python3/#python-3-surrogate-handling
LC_ALL=C.UTF-8
LANG=C.UTF-8

39
.eslintrc.json Normal file
View File

@ -0,0 +1,39 @@
{
"extends": "airbnb-base",
"env": {
"node": true,
"es6": true,
"browser": true,
"jest/globals": true
},
"globals": {
"io": true,
"webkitSpeechRecognition": true
},
"plugins": [
"import",
"jest"
],
"settings": {
"import/resolver": {
"babel-module": { }
}
},
"rules": {
"no-multi-assign": "off",
"no-trailing-spaces": "off",
"linebreak-style": "off",
"indent": "off",
"strict": "off",
"no-console": "off",
"no-param-reassign": "off",
"no-shadow": "off",
"import/no-extraneous-dependencies": "off",
"import/no-dynamic-require": "off",
"no-underscore-dangle": ["error", { "allowAfterThis": true }],
"space-before-function-paren": ["error", "always"],
"prefer-destructuring": ["error", { "array": true, "object": true }],
"comma-dangle": ["error", "never"],
"semi": ["error", "never"]
}
}

76
.github/CODE_OF_CONDUCT.md vendored Normal file
View File

@ -0,0 +1,76 @@
# Contributor Covenant Code of Conduct
## Our Pledge
In the interest of fostering an open and welcoming environment, we as
contributors and maintainers pledge to making participation in our project and
our community a harassment-free experience for everyone, regardless of age, body
size, disability, ethnicity, sex characteristics, gender identity and expression,
level of experience, education, socio-economic status, nationality, personal
appearance, race, religion, or sexual identity and orientation.
## Our Standards
Examples of behavior that contributes to creating a positive environment
include:
* Using welcoming and inclusive language
* Being respectful of differing viewpoints and experiences
* Gracefully accepting constructive criticism
* Focusing on what is best for the community
* Showing empathy towards other community members
Examples of unacceptable behavior by participants include:
* The use of sexualized language or imagery and unwelcome sexual attention or
advances
* Trolling, insulting/derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or electronic
address, without explicit permission
* Other conduct which could reasonably be considered inappropriate in a
professional setting
## Our Responsibilities
Project maintainers are responsible for clarifying the standards of acceptable
behavior and are expected to take appropriate and fair corrective action in
response to any instances of unacceptable behavior.
Project maintainers have the right and responsibility to remove, edit, or
reject comments, commits, code, wiki edits, issues, and other contributions
that are not aligned to this Code of Conduct, or to ban temporarily or
permanently any contributor for other behaviors that they deem inappropriate,
threatening, offensive, or harmful.
## Scope
This Code of Conduct applies both within project spaces and in public spaces
when an individual is representing the project or its community. Examples of
representing a project or community include using an official project e-mail
address, posting via an official social media account, or acting as an appointed
representative at an online or offline event. Representation of a project may be
further defined and clarified by project maintainers.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported by contacting the project team at <louis.grenard@gmail.com>. All
complaints will be reviewed and investigated and will result in a response that
is deemed necessary and appropriate to the circumstances. The project team is
obligated to maintain confidentiality with regard to the reporter of an incident.
Further details of specific enforcement policies may be posted separately.
Project maintainers who do not follow or enforce the Code of Conduct in good
faith may face temporary or permanent repercussions as determined by other
members of the project's leadership.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see
https://www.contributor-covenant.org/faq

114
.github/CONTRIBUTING.md vendored Normal file
View File

@ -0,0 +1,114 @@
# Contributing
Thanks a lot for your interest in contributing to Leon! :heart:
**Leon needs open-source to live**, the more modules he has, the more skillful he becomes.
**Before submitting your contribution**, please take a moment to review this document.
Please note we have a [code of conduct](https://github.com/leon-ai/leon/blob/develop/.github/CODE_OF_CONDUCT.md), please follow it in all your interactions with the project.
## How You Can Help
Here are few examples about how you could help on Leon, by:
- [Creating a new module](https://docs.getleon.ai/packages-modules.html).
- [Working on new features](https://roadmap.getleon.ai) (what is in backlog or todo).
- [Suggesting new ideas](https://github.com/leon-ai/leon/issues/new/choose).
- [Reporting a bug](https://github.com/leon-ai/leon/issues/new?labels=bug&template=BUG.md).
- [Improving the documentation](https://github.com/leon-ai/docs.getleon.ai) (translations, typos, better writing, etc.).
- [Buying me a fruit juice](https://donate.getleon.ai).
## Pull Requests
**Working on your first Pull Request?** You can learn how from this *free* series [How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github).
- **Please first discuss** the change you wish to make via [issue](https://github.com/leon-ai/leon/issues),
email, or any other method with the owners of this repository before making a change.
It might avoid a waste of your time.
- The `master` branch is actually used as a snapshot of the latest stable release. **Do not submit your PRs
against the `master` branch**.
- Ensure your code **respect our coding standards** (cf. [.eslintrc.json](https://github.com/leon-ai/leon/blob/develop/.eslintrc.json)).
To do so, you can run:
```sh
npm run lint
```
- Make sure your **code passes the tests**. You can run the tests via the following command:
```sh
npm test
```
If you're adding new features to Leon, please include tests.
## Development Setup
- Follow the [basic setup](https://github.com/leon-ai/leon/blob/develop/README.md#getting-started).
- Then:
```sh
# run development server
npm run dev:server
# run development web app
npm run dev:app
# run the wake word node (optional)
npm run wake
```
## Versioning
- We use [Semantic Versioning](https://semver.org) for releases.
- A new Leon module creation increases the MINOR version number of its relevant Leon package.
- Each time a MAJOR or MINOR version number of a Leon package is increased, then the MINOR version number of the project should also be increased.
- Each time a PATCH version number of a Leon package is increased, then the PATCH version number of the projet should also be increased.
## Commits
The commit message guideline is adapted from the [AngularJS Git Commit Guidelines](https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#-git-commit-guidelines).
### Types
Types define which kind of changes you made to the project.
| Types | Description |
| ------------- |-------------|
| BREAKING | Changes including breaking changes. |
| build | New build version. |
| chore | Changes to the build process or auxiliary tools such as changelog generation. No production code change. |
| docs | Documentation only changes. |
| feat | A new feature. |
| fix | A big fix. |
| perf | A code change that improves performance. |
| refactor | A code change that neither fixes a bug nor adds a feature. |
| style | Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc.). |
| test | Adding missing or correcting existing tests. |
### Scopes
Scopes define high-level nodes of Leon.
- web app
- server
- hotword
- package/package_name
### Examples
```sh
git commit -m "feat(server): awesome new server feature"
git commit -m "docs(package/leon): fix spelling"
git commit -m "chore: split training script into awesome blocks"
git commit -m "style(web app): remove chatbot useless parentheses"
```
## Donate
You can also contribute by [buying me a fruit juice](https://donate.getleon.ai).
## Spread the Word
Use [#LeonAI](https://twitter.com/hashtag/LeonAI) if you tweet about Leon and/or mention [@louistiti_fr](https://twitter.com/louistiti_fr).

7
.github/ISSUE_TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,7 @@
<!--
Thanks for your interest in Leon! ❤️
Please check if there is no similar issue before creating this one.
-->

26
.github/ISSUE_TEMPLATE/BUG.md vendored Normal file
View File

@ -0,0 +1,26 @@
---
name: 🐞 Bug Report
about: Report an unexpected problem or unintended behavior.
labels: bug
---
<!--
Thanks for your interest in Leon! ❤️
Please check if there is no similar issue before creating this one.
-->
### Specs
- Leon version:
- OS (or browser) version:
- Node.js version:
- Complete "npm run check" output:
- (optional) Leon package version:
### Expected Behavior
### Actual Behavior
### How Do We Reproduce?
### Extra (like a sample repo to reproduce the issue, etc.)

22
.github/ISSUE_TEMPLATE/DOCS.md vendored Normal file
View File

@ -0,0 +1,22 @@
---
name: 📝 Documentation
about: Are the docs missing, confusing, etc.? Tell us more about it.
---
<!--
Thanks for your interest in Leon! ❤️
If it is related to https://docs.getleon.ai, please open an issue there: https://github.com/leon-ai/docs.getleon.ai/issues.
Please check if there is no similar issue before creating this one.
Please place an x (no spaces - [x]) in all [ ] that apply.
-->
### Documentation Is:
- [ ] Missing
- [ ] Needed
- [ ] Confusing
- [ ] Not Sure?
### Explanation
### Proposal

View File

@ -0,0 +1,14 @@
---
name: ✨ Feature Request
about: Suggest a new feature idea.
labels: feature request
---
<!--
Thanks for your interest in Leon! ❤️
Please check if there is no similar issue before creating this one.
-->
### Feature Use Case
### Feature Proposal

16
.github/ISSUE_TEMPLATE/IMPROVEMENT.md vendored Normal file
View File

@ -0,0 +1,16 @@
---
name: 🔧 Improvement
about: Suggest an idea which is not a feature.
labels: improvement
---
<!--
Thanks for your interest in Leon! ❤️
Please check if there is no similar issue before creating this one.
-->
### Expected Behavior
### Actual Behavior
### Proposal

14
.github/ISSUE_TEMPLATE/QUESTION.md vendored Normal file
View File

@ -0,0 +1,14 @@
---
name: ❓ Question
about: Ask a question about Leon.
labels: question
---
<!--
Thanks for your interest in Leon! ❤️
Please check if there is no similar issue before creating this one.
Please ask one question per issue.
-->
### Question

30
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,30 @@
<!--
Thanks a lot for your interest in contributing to Leon! :heart:
Please first discuss the change you wish to make via issue,
email, or any other method with the owners of this repository before making a change.
It might avoid a waste of your time.
Before submitting your contribution, please take a moment to review this document:
https://github.com/leon-ai/leon/blob/develop/.github/CONTRIBUTING.md
Please place an x (no spaces - [x]) in all [ ] that apply.
-->
### What type of change does this PR introduce?
- [ ] Bugfix
- [ ] Feature
- [ ] Refactor
- [ ] Documentation
- [ ] Not Sure?
### Does this PR introduce breaking changes?
- [ ] Yes
- [ ] No
### List any relevant issue numbers:
### Description:

26
.gitignore vendored Normal file
View File

@ -0,0 +1,26 @@
__pycache__/
.idea/
**/dist/
**/node_modules/
test/coverage/
**/tmp/*
bridges/python/.venv/*
downloads/*
logs/*
server/src/config/**/*.json
bin/deepspeech/*
bin/flite/*
*.pyc
.DS_Store
npm-debug.log
debug.log
.env
!**/.gitkeep
!bridges/python/.venv/.gitkeep
!**/*.sample*
packages/**/config/config.json
packages/**/data/db/*.json
server/src/data/expressions/classifier.json
app/js/main.js
package.json.backup

335
CHANGELOG.md Normal file
View File

@ -0,0 +1,335 @@
# [1.0.0-beta.0](https://github.com/Louistiti/leon/compare/https://github.com/Louistiti/leon.git...v1.0.0-beta.0) (2019-02-10)
### BREAKING CHANGES
- new CI
([9dac4c83](https://github.com/Louistiti/leon/commit/9dac4c83759e56dc5f69ddff895ba1bc6034b442))
- new CI
([705f81c4](https://github.com/Louistiti/leon/commit/705f81c41588f6dd7ca657ffa2007a04375740a6))
- rename `*-sample*` files to `*.sample*`
([7e3ab427](https://github.com/Louistiti/leon/commit/7e3ab427b6150abad8f2ccdc24228ef2ab6d94d2))
- get rid of gulp.js
([c5676cf2](https://github.com/Louistiti/leon/commit/c5676cf271473d083b377897ac928c7c68c9e831))
- **server:**
- new Watson STT
([deb854b2](https://github.com/Louistiti/leon/commit/deb854b2d6e80b32e009b13b3e3cc067f05ae0bc))
- new Watson TTS
([a5a6d72b](https://github.com/Louistiti/leon/commit/a5a6d72bda2b0b35b03bbbe2c09555b55f605d9e))
- switch NLP to NLU
([32fe88b1](https://github.com/Louistiti/leon/commit/32fe88b1ef26c6ab73066c76c8fc3cd5d2b7b1a4))
- binary TTS and STT structure
([11689e88](https://github.com/Louistiti/leon/commit/11689e88627dfd02e961b7f3bfaa0e5ff2c0333d))
### Features
- preinstall script
([0644296b](https://github.com/Louistiti/leon/commit/0644296b10e857c6811555ab30bad6236d124e60))
- preinstall script
([6793c9f6](https://github.com/Louistiti/leon/commit/6793c9f61ba323b9ee453d5992a8115e41f54131))
- preinstall script
([a53be750](https://github.com/Louistiti/leon/commit/a53be7504c6c8ba3eee5966d2190190733bde069))
- preinstall script
([82299099](https://github.com/Louistiti/leon/commit/82299099600ca7929534bcca4243be01fae1da73))
- preinstall script
([1b0b3e9e](https://github.com/Louistiti/leon/commit/1b0b3e9e33fe8fec0415e1985303610aa002ce3a))
- preinstall script
([231d68d6](https://github.com/Louistiti/leon/commit/231d68d6c7d0ea366784cfd28da95f3a9012cfd5))
- preinstall script
([fb45d9ed](https://github.com/Louistiti/leon/commit/fb45d9ed744d0c473fab7f79e90c25d6723d9b99))
- check script
([e545ac58](https://github.com/Louistiti/leon/commit/e545ac5847f6e38649a494730b99e9a002015470))
- chatbot visual almost done for the v1
([94a2f557](https://github.com/Louistiti/leon/commit/94a2f55757bf06487a2fbd8c84c1eff677568516))
- **package/leon:**
- add partner assistant module
([794a4d73](https://github.com/Louistiti/leon/commit/794a4d73957cd6b5d7d434b8727426e8a31d0618))
- add sentence to the whoami module
([a241cdc0](https://github.com/Louistiti/leon/commit/a241cdc0bf04515b18bbc40ddb117e092851afd2))
- **client:**
voice detection
([eec7fc8d](https://github.com/Louistiti/leon/commit/eec7fc8d8f3e28cfd2d844c4319239d88609c77a))
- **devapp:**
disable mic when the STT is disabled
([e0bf1163](https://github.com/Louistiti/leon/commit/e0bf116360a5b3b43618c04b352cd97f70a161b2))
- **hotword:**
server and client ready
([a5a8f932](https://github.com/Louistiti/leon/commit/a5a8f9321daff80d52c5a2454281317acb61db7e))
- **hotword + after-speech:**
done
([db813296](https://github.com/Louistiti/leon/commit/db813296dea8c7f702d186762758bdfe2fad0251))
- **module:**
- Bye done
([e178de05](https://github.com/Louistiti/leon/commit/e178de0513f5867257621a50b5dabc36790fc64f))
- Random Number done + Meaning of Life done + Welcome done
([5a3b3f75](https://github.com/Louistiti/leon/commit/5a3b3f75e59395ef078cfa3fceca3d89fd364cfe))
- Greeting done
([8a6fa9c5](https://github.com/Louistiti/leon/commit/8a6fa9c52cd1c82c46ed21c4f4af83f615da4bc4))
- Joke done
([367318f7](https://github.com/Louistiti/leon/commit/367318f75101822409f0f00be315f805340a3fe4))
- **core:**
- NLP fallback + confidence for each language + French translations written
([d8004d12](https://github.com/Louistiti/leon/commit/d8004d1217d146de825ae356d97b97b2d511b192))
- STT + TTS translations architecture
([447efd21](https://github.com/Louistiti/leon/commit/447efd218af96a9c472cbd059ac8813a756640d1))
- **stt:**
- Google Cloud STT implemented
([216765cc](https://github.com/Louistiti/leon/commit/216765cc42f2ec683233f093152f3ce8b048508d))
- ASR class created + delete audios file after STT job
([69b1fcce](https://github.com/Louistiti/leon/commit/69b1fcce77786cf64aea1af0e643ab0dd7298550))
- done + need to prepare init models thanks to a thread
([46d36990](https://github.com/Louistiti/leon/commit/46d36990398bc2179658efb00ffbbc38cc18a735))
- client send stream to server + server convert stream to a WAVE file
([67ceb99a](https://github.com/Louistiti/leon/commit/67ceb99a9c2620f4ab4e8510c6ef37eb2723d428))
- **stt and tts:**
Watson implemented
([3dae3d68](https://github.com/Louistiti/leon/commit/3dae3d6811febf0c25032752a07c9fcd26d9580e))
- **test:**
this is a test. Close #1
([5efe707d](https://github.com/Louistiti/leon/commit/5efe707df7e63c8c12fd3ce54d31b858da75541e))
- **tts:**
- Amazon Polly implemented
([cd5dbe7a](https://github.com/Louistiti/leon/commit/cd5dbe7a0fa9cd958d4c355db44ee3f82a465b16))
- Google Cloud TTS implemented
([eb3e18e8](https://github.com/Louistiti/leon/commit/eb3e18e82f51f59eb5c7c9fb3afab68d39c04939))
- done
([2e5d3f18](https://github.com/Louistiti/leon/commit/2e5d3f18692f8a45304d9b3d1c0689fd4124c9f0))
- architecture done + need to delete audio files + forward buffer to the client
([85664eef](https://github.com/Louistiti/leon/commit/85664eefc1839dfc946dbc17a55a96119089c6ac))
- **web app:**
- add favicon
([6d0ae10d](https://github.com/Louistiti/leon/commit/6d0ae10ddb8ba478ad9218a1c23fa686b55555c9))
- avoid N - 1 duplicate to browse commands history
([930eac09](https://github.com/Louistiti/leon/commit/930eac094a61d16013ab4fc320e2a28b853c6f38))
- chatbot done
([520bef6d](https://github.com/Louistiti/leon/commit/520bef6d8e8ab51f0c2478e7b00d1dd34d03ee24))
### Bug Fixes
- Python output switch key to code
([8a7d6124](https://github.com/Louistiti/leon/commit/8a7d6124aeac533f84a23d21b60f6ab51be3b848))
- setup-stt wget
([c1d7a682](https://github.com/Louistiti/leon/commit/c1d7a6827128b97645a2ab610c0e6ae1a1b1188c))
- dev build app
([53681a55](https://github.com/Louistiti/leon/commit/53681a55aee8af803e8a4cb4ba1ea87f7823121e))
- Windows setup
([7609a509](https://github.com/Louistiti/leon/commit/7609a5092a2ce1396387288c6c2df15a6ed9328b))
- setup offline
([c74134b4](https://github.com/Louistiti/leon/commit/c74134b46d2e15d66599ddb0b0dee63f8fc2faed))
- offline setup on Linux
([3278aad6](https://github.com/Louistiti/leon/commit/3278aad6c4b412b5d523482a0e4b4c8ba82ed006))
- build script
([732f9027](https://github.com/Louistiti/leon/commit/732f902755699e5dbb8bc03296f6f326cde83dfe))
- setup env vars
([3606edc4](https://github.com/Louistiti/leon/commit/3606edc4934636c81d74ddf6c42d38b76b75e7e8))
- .env LANG and LC_ALL
([ff652bb5](https://github.com/Louistiti/leon/commit/ff652bb5193d138532901f1eb5fbe63510fb5fae))
- brain TTS init when enabled
([3697c89a](https://github.com/Louistiti/leon/commit/3697c89a3669caee75967152433b3066bd310aa3))
- multiple things
([42bdb88a](https://github.com/Louistiti/leon/commit/42bdb88a68f4e6ab4d48583f6ba934b07f61bb52))
- setup python packages checking
([843a3012](https://github.com/Louistiti/leon/commit/843a3012bfa7864dd34ea513d400a87b3b4e1297))
- Python version requirements
([acebd80b](https://github.com/Louistiti/leon/commit/acebd80b0c99aa703218048d94191121175e9833))
- chore training
([68d559a3](https://github.com/Louistiti/leon/commit/68d559a3a17a9d31700534036af23aaa1c98c08e))
- CHANGELOG generator
([574847b5](https://github.com/Louistiti/leon/commit/574847b5bb7cbadad577f522aeac0546042d354b))
- this is a fix
([3e62378e](https://github.com/Louistiti/leon/commit/3e62378eff270b6beb7fa4dc4bb368f3859d806b))
- **.gitignore:**
.json TinyDB
([13fdcd63](https://github.com/Louistiti/leon/commit/13fdcd6307115c1409dd1389d27bbe5d6b213d04))
- **changelog:**
happy to fix that bug
([276a78d1](https://github.com/Louistiti/leon/commit/276a78d17e2c37371412b94e1b19971b4378a099))
- **config:**
voice samples
([c497c685](https://github.com/Louistiti/leon/commit/c497c68560525bc718be83c41edb45c5cebbb75b))
- **devapp:**
do not send query while writing
([8344d861](https://github.com/Louistiti/leon/commit/8344d8613e4fe737ab187a4a74f61d038fb8e5da))
- **package/leon:**
- "à bon escient"
([41868833](https://github.com/Louistiti/leon/commit/41868833e1659c9b4f6752b4ed190acaadff3c09))
- Siri answer
([a9a2d469](https://github.com/Louistiti/leon/commit/a9a2d469bb21b7f10c805b77f1efa8905a5b3d67))
- **server:**
- ASR FFmpeg mono channel
([49c5abfc](https://github.com/Louistiti/leon/commit/49c5abfc7c56f136ff4ab12e010b1b7ece750163))
- brain execution was stopping after the first query
([9da0c9a9](https://github.com/Louistiti/leon/commit/9da0c9a9c0a9de5a2d3825b773a8d26335eb71ef))
- **setup:**
core files path
([6a3fb9b0](https://github.com/Louistiti/leon/commit/6a3fb9b08cfa75556181c742a41eb5f05512e938))
- **test:**
- latest test
([3bd2e95a](https://github.com/Louistiti/leon/commit/3bd2e95afabde3f549110681a5497183ea98b0dc))
- this is another fix
([9de09067](https://github.com/Louistiti/leon/commit/9de09067d4ddeb840e8eac4e91b0f4990025702c))
- **web app:**
allow init even when mic is not allowed
([da469d9d](https://github.com/Louistiti/leon/commit/da469d9df34920b6c542d75d9046c7fdedf9bf0b))
### Performance Improvements
- improve for loop
([f3c64495](https://github.com/Louistiti/leon/commit/f3c644956d724b0ef684cd5a5314bfd25c9bfcb7))
### Documentation Changes
- update logo size
([df813c49](https://github.com/Louistiti/leon/commit/df813c4958bb7b0febf4710595ad7d2d1dac4ad8))
- README edit video preview
([b77f86f4](https://github.com/Louistiti/leon/commit/b77f86f4a09fd1f55e2bca8125d7fc034aa4914a))
- README video
([41761226](https://github.com/Louistiti/leon/commit/417612264fdfb5280f2b7567788501304de45966))
- CONTRIBUTING adding roadmap
([fd99683f](https://github.com/Louistiti/leon/commit/fd99683ff1bed00880832d5467aa2613b8ebd4ca))
- add Twitter in the README "Stay Tuned" section
([c6dfef7a](https://github.com/Louistiti/leon/commit/c6dfef7a7f56381064c8672f11ece308e810be29))
- badges updated
([f3dcb203](https://github.com/Louistiti/leon/commit/f3dcb203d14f49c0dac5d9d11b663a4908e09e7b))
- typo fix
([cae01084](https://github.com/Louistiti/leon/commit/cae01084e3a4611c32b83ab08faada51f841d808))
- improve README
([087a02bf](https://github.com/Louistiti/leon/commit/087a02bfa76892986c7e173fbbb703ef8fe70513))
- supported OS
([91353dee](https://github.com/Louistiti/leon/commit/91353deef673d39b9dce6fb51d0d7862edf9362a))
- improve README
([6c201e27](https://github.com/Louistiti/leon/commit/6c201e2709567f510f6a5fb93d34715f1f9f682b))
- improve README
([ec1406c2](https://github.com/Louistiti/leon/commit/ec1406c215245a7761c89de8256759c8017c103f))
- improve README
([0cdc683e](https://github.com/Louistiti/leon/commit/0cdc683ec65c32907308926e703915405f723233))
- CONTRIBUTING improvement
([7dba9928](https://github.com/Louistiti/leon/commit/7dba9928ce8e2df2b7dc26b3ac2671779edb23e3))
- contributing improvement
([6716585b](https://github.com/Louistiti/leon/commit/6716585b5250b1309d796e36ee2d865b736bd17f))
- italic README catchphrase
([dad0c268](https://github.com/Louistiti/leon/commit/dad0c2681a82432ee292b145f400befd5f537a07))
- tiny README change
([633f5f29](https://github.com/Louistiti/leon/commit/633f5f29e5b32f565cb6c7d53ec309aba77b0121))
- improve README
([4ee74263](https://github.com/Louistiti/leon/commit/4ee74263fd52028cd1a12479990531fe1b9edba4))
- improve README
([2541e466](https://github.com/Louistiti/leon/commit/2541e46677556427f49e484cbdf54496859da24e))
- improve README
([d469c9e5](https://github.com/Louistiti/leon/commit/d469c9e5777819afeec671b2951d360375891164))
- edit donation link
([c1b3e45e](https://github.com/Louistiti/leon/commit/c1b3e45e3a397e424d42d59348bf8b3425698858))
- adjust few things
([abf3bbc5](https://github.com/Louistiti/leon/commit/abf3bbc5a70c4da3007b648428713757f9568e4a))
- README or to OR
([7d10ef26](https://github.com/Louistiti/leon/commit/7d10ef26ba24d59fc1c0d6b041d1d14bbed7f9e1))
- fix
([22b1ab5b](https://github.com/Louistiti/leon/commit/22b1ab5bc6a468d7ffe23e1857acca8c9301ef92))
- fix
([11a6df36](https://github.com/Louistiti/leon/commit/11a6df36fa9d030cac063a15057a7463eca0684f))
- docs.getleon.ai redirect
([bba41cd9](https://github.com/Louistiti/leon/commit/bba41cd97d41858781185f5cd5866dde3f2cb754))
- sentence improvement
([18720175](https://github.com/Louistiti/leon/commit/1872017535c36c16ca59ca2ee9c378244159fd5c))
- improve commit message description
([3d9698bf](https://github.com/Louistiti/leon/commit/3d9698bffc68413c7c429725eae45cf3edecc1db))
- README.md done
([300b1538](https://github.com/Louistiti/leon/commit/300b15382d0fb7ecf9c5275304c0798f8eaadb5c))
- README
([105965f3](https://github.com/Louistiti/leon/commit/105965f3ab65cfc7157e66d07fff4ba0065b863b))
- README
([6b089d16](https://github.com/Louistiti/leon/commit/6b089d168a80ce197da1e38f958a3df802d72f6b))
- README
([1e4eb629](https://github.com/Louistiti/leon/commit/1e4eb629924290e2b47c4e078e8085faeea9149d))
- README
([e8d6039f](https://github.com/Louistiti/leon/commit/e8d6039f1ef073599c847ef0cc5fb43c65505a89))
- README
([d999bdc3](https://github.com/Louistiti/leon/commit/d999bdc385768f2d87427ba1cc229aa71c0855f2))
- QUESTION.md quick fix
([05c74653](https://github.com/Louistiti/leon/commit/05c74653fdbc86173f8042dd17f1f74f048a3c20))
- QUESTION.md quick fix
([6a94f9da](https://github.com/Louistiti/leon/commit/6a94f9da691217e9590a78ac53622db50f953259))
- ISSUE_TEMPLATE/QUESTION.md
([6f97614b](https://github.com/Louistiti/leon/commit/6f97614b6d16c4ebaa0561393efb996519dc2426))
- ISSUE_TEMPLATE/DOCS.md
([ba4b83f1](https://github.com/Louistiti/leon/commit/ba4b83f1b0ac514648294ecfd466d4188bc8d5d0))
- ISSUE_TEMPLATE/FEATURE_REQUEST.mdt and ISSUE_TEMPLATE/IMPROVEMENT.md
([f48ad692](https://github.com/Louistiti/leon/commit/f48ad692d264f84345631f3f79cea1fac5a5748b))
- delete tmp BUG.md
([3e76cf29](https://github.com/Louistiti/leon/commit/3e76cf29dcb95e263339850127b4ef7590feb762))
- ISSUE_TEMPLATE/BUG.md
([efcbdffc](https://github.com/Louistiti/leon/commit/efcbdffc026608302a1bbfce160bf0d995a356ca))
- README.md quick fix
([c9414490](https://github.com/Louistiti/leon/commit/c9414490c9956e35ae466973d4fad92f87053561))
- README.md quick fix
([8f242248](https://github.com/Louistiti/leon/commit/8f2422481e5eabc151f6f8adea04772dc6045669))
- README.md skeleton
([6ab10817](https://github.com/Louistiti/leon/commit/6ab108179c90dc06f71a7a5acc9e6d7f6f7c4539))
- ISSUE_TEMPLATE.md quick fix
([e2966435](https://github.com/Louistiti/leon/commit/e296643598dc10e4afb75472e71f300aaf16ca73))
- ISSUE_TEMPLATE.md done
([e6149798](https://github.com/Louistiti/leon/commit/e6149798b0021bacf9ad8a0ed1d2ed47c489098f))
- ISSUE_TEMPLATE.md heart
([180a329c](https://github.com/Louistiti/leon/commit/180a329c71edb55437d59d3753974b60323903bc))
- ISSUE_TEMPLATE.md tmp
([b04b1acc](https://github.com/Louistiti/leon/commit/b04b1acc3558b6193c95a0d26dc664519fdbeac3))
- PULL_REQUEST_TEMPLATE.md
([25264a80](https://github.com/Louistiti/leon/commit/25264a808900d0713418ec0b2d440951052e01fc))
- finale CONTRIBUTING.md
([776088fc](https://github.com/Louistiti/leon/commit/776088fc7f2d106b12b2a0df0fd9fcec66568651))
- quick fix
([51742875](https://github.com/Louistiti/leon/commit/51742875e50b9513415beabc5dd0ef926a125628))
- quick fix
([b21e8553](https://github.com/Louistiti/leon/commit/b21e8553a01c1088d4450ff19f192b602675d66d))
- add heart to CONTRIBUTING.md
([3e818c6e](https://github.com/Louistiti/leon/commit/3e818c6ec309cabab5eee220f0dbd20b6e77a28a))
- CONTRIBUTING.md
([9c93fdf0](https://github.com/Louistiti/leon/commit/9c93fdf02d7e4543e01164fae3f7a5bba38e837f))
- Code of Conduct latest version
([e09c0785](https://github.com/Louistiti/leon/commit/e09c0785274774e95802780a3fc183a39230bde1))
- Code of Conduct
([b170adb8](https://github.com/Louistiti/leon/commit/b170adb8f3183cab2dbc83dd0850eec03764776d))
- small changes on README.md and LICENSE.md
([45650d36](https://github.com/Louistiti/leon/commit/45650d3602606571f27e73aed9955871ec8fb5b2))
- clean CHANGELOG
([5d32522b](https://github.com/Louistiti/leon/commit/5d32522b4904264de074ff6e063b28c72f746c02))
- README PRs welcome badge
([dee8eb99](https://github.com/Louistiti/leon/commit/dee8eb99fc8fcd181cdf6799120448a38e958fdf))
- README PRs welcome badge
([f141153e](https://github.com/Louistiti/leon/commit/f141153e02be33f56a5b4e95f4f260a07c078963))
- README badges
([75b6abf7](https://github.com/Louistiti/leon/commit/75b6abf783f32fde5ca4df5e329046da3701edac))
- update README
([2ad16984](https://github.com/Louistiti/leon/commit/2ad16984c62c3190efd3c1c0c66709da047246a1))
- license updated
([b5c87cfb](https://github.com/Louistiti/leon/commit/b5c87cfb4da79fc492c14f048f44ce85ba6526e0))
- this is a test
([e54758fe](https://github.com/Louistiti/leon/commit/e54758fe63a15e70cd9618279ec577cb99d28294))
- docs(CHANGELOG.md)
([e289a74c](https://github.com/Louistiti/leon/commit/e289a74ccca82ee20b8b7e453282d3fc61dd1e0f))
- **changelog:**
- reset
([d9739c81](https://github.com/Louistiti/leon/commit/d9739c81d204d317d19dd3c27311e7861bb0a90a))
- here is a new test
([2aabbf26](https://github.com/Louistiti/leon/commit/2aabbf262b4900287aa324098d3a7cf6020ab8fa))
- add new chore
([8eaa9c75](https://github.com/Louistiti/leon/commit/8eaa9c757929ec648720c05866c23a0aa9548a8c))
- just few tests
([0948adbe](https://github.com/Louistiti/leon/commit/0948adbeba05e71eaa0f17dee57b8a62b216826f))
- **package.json:**
homepage URL
([d82f34d0](https://github.com/Louistiti/leon/commit/d82f34d069ac7c14ddecc07dad1689b752c3fb61))
- **package/checker:**
README
([6ddedd24](https://github.com/Louistiti/leon/commit/6ddedd24a5f10a594ac402bb256f7feb01f70f16))
- **package/leon:**
README
([3cde3273](https://github.com/Louistiti/leon/commit/3cde3273f6b0fa207d0bcd4b85040843eec4f3ba))
- **package/videodownloader:**
- quick fix
([bf16c2c6](https://github.com/Louistiti/leon/commit/bf16c2c64cdd0b596762761e86fb9917efef22f3))
- README
([3ac3f47d](https://github.com/Louistiti/leon/commit/3ac3f47d4df716be8025096bd5fd8e4cf9c5393a))
- **readme:**
- specify recommended virtual env
([d98cfb72](https://github.com/Louistiti/leon/commit/d98cfb72589fb257015e4eabc182b6ae1b497d85))
- another test
([cae8cf8f](https://github.com/Louistiti/leon/commit/cae8cf8f46ee25a06ae87c878bdfd39e5efc15f8))
- alphabetical testing
([6fcc867d](https://github.com/Louistiti/leon/commit/6fcc867dbcec1b34239d9d4d73d0fe26bc8f57ec))

21
LICENSE.md Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019-present, Louis Grenard <louis.grenard@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

130
README.md Normal file
View File

@ -0,0 +1,130 @@
<p align="center">
<a href="https://getleon.ai"><img width="96" src="https://getleon.ai/img/logo.svg"></a><br><br>
<a href="https://www.youtube.com/watch?v=p7GRGiicO1c"><img width="512" src="https://getleon.ai/img/1.0.0-beta.0_preview_en.png"></a><br>
</p>
<h1 align="center">Leon</h1>
*<p align="center">Your open-source personal assistant.</p>*
<p align="center">
<a href="https://github.com/leon-ai/leon/blob/develop/LICENSE.md"><img src="https://img.shields.io/badge/license-MIT-blue.svg?style=flat"/></a>
<a href="https://circleci.com/gh/leon-ai/leon/tree/develop"><img src="https://img.shields.io/circleci/project/github/leon-ai/leon.svg?style=flat"/></a>
<a href="https://github.com/leon-ai/leon/blob/develop/.github/CONTRIBUTING.md"><img src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat"/></a>
</p>
<p align="center">
<a href="https://getleon.ai">Website</a> ::
<a href="https://docs.getleon.ai">Documentation</a> ::
<a href="https://roadmap.getleon.ai">Roadmap</a> ::
<a href="https://github.com/leon-ai/leon/blob/develop/.github/CONTRIBUTING.md">Contributing</a>
</p>
---
## Introduction
Leon is an **open-source personal assistant** who can live **on your server**.
He **does stuff** when you **ask for it**.
You can **talk to him** and he can **talk to you**.
You can also **text him** and he can also **text you**.
If you want to, Leon can communicate with you by being **offline to protect your privacy**.
### Why?
> 1. If you are a developer (or not), you may want to build many things that could help in your daily life.
> Instead of building a dedicated project for each of those ideas, Leon can help you with his
> packages/modules (skills) structure.
> 2. With this generic structure, everyone can create his own modules and share them with others.
> Therefore there is only one core (to govern all of them).
> 3. Leon uses AI concepts, which is cool.
> 4. Privacy matters, you can configure Leon to talk with him offline. You can already text with him without any third party services.
> 5. Open-source is great.
### What is this repository for?
> This repository contains the following nodes of Leon:
> - The server
> - The packages/modules
> - The web app
> - The hotword node
Sounds good for you? Then let's get started!
## Getting Started
### Prerequisites
- [Node.js](https://nodejs.org/) >= 10
- npm >= 5
- [Python](https://www.python.org/downloads/) 3.6.x
- [Pipenv](https://docs.pipenv.org)
- Supported OSes: Linux, macOS and Windows
To install these prerequisites, you can follow the [How To section](https://docs.getleon.ai/how-to/) of the documentation.
### Installation
```sh
# Clone the repository (stable branch)
git clone -b master https://github.com/leon-ai/leon.git leon
# OR download the latest release at: https://github.com/leon-ai/leon/releases/latest
# Go to the project root
cd leon
# Install
npm install
```
### Usage
```sh
# Check the setup went well
npm run check
# Build
npm run build
# Run
npm start
# Go to http://localhost:1337
# Hooray! Leon is running
```
## Documentation
For full documentation, visit [docs.getleon.ai](https://docs.getleon.ai).
## Roadmap
To know what is going on, follow [roadmap.getleon.ai](https://roadmap.getleon.ai).
## Contributing
If you have an idea about improving Leon, do not hesitate.
**Leon needs open-source to live**, the more modules he has, the more skillful he becomes.
## Stay Tuned
- [Newsletter](https://getleon.ai)
- [Blog](https://blog.getleon.ai)
- [GitHub issues](https://github.com/leon-ai/leon/issues)
- [Twitter](https://twitter.com/louistiti_fr)
- [#LeonAI](https://twitter.com/hashtag/LeonAI)
## Author
**Louis Grenard** ([@louistiti_fr](https://twitter.com/louistiti_fr))
## Donate
You can also contribute by [buying me a fruit juice](https://donate.getleon.ai).
## License
[MIT License](https://github.com/leon-ai/leon/blob/develop/LICENSE.md)
Copyright (c) 2019-present, Louis Grenard <louis.grenard@gmail.com>
## Cheers!
![Cheers!](https://assets-cdn.github.com/images/icons/emoji/unicode/1f37b.png?v6 "Cheers!")

289
app/css/style.css Normal file
View File

@ -0,0 +1,289 @@
@import url(https://fonts.googleapis.com/css?family=Open+Sans:400,600,700,800);
html, body, div, span, applet, object, iframes,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, sub, sup, tt, var,
u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
margin: 0;
padding: 0;
border: 0;
font-size: 100%;
font: inherit;
vertical-align: baseline;
}
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
display: block;
}
blockquote, q {
quotes: none;
}
blockquote:before, blockquote:after,
q:before, q:after {
content: '';
content: none;
}
table {
border-collapse: collapse;
border-spacing: 0;
}
* {
box-sizing: border-box;
outline: none;
}
a {
color: inherit;
}
body {
color: #FFF;
background-color: #151718;
font-family: 'Open Sans', sans-serif;
font-weight: 400;
}
body > * {
transition: opacity .5s;
}
body.settingup > * {
opacity: 0;
}
body.settingup::after {
position: absolute;
content: '';
width: 32px;
height: 32px;
background-color: #777;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border-radius: 50%;
animation: scaleout .6s infinite ease-in-out;
}
@keyframes scaleout {
0% {
transform: scale(0);
}
100% {
transform: scale(1.0);
opacity: 0;
}
}
main {
position: absolute;
width: 63%;
top: 0;
left: 50%;
height: 100%;
transform: translate(-50%, 0);
}
footer {
position: absolute;
text-align: center;
left: 50%;
bottom: 0;
transform: translate(-50%, -50%);
}
input {
text-align: center;
color: #FFF;
width: 100%;
border: none;
border-bottom: 2px solid #FFF;
background: none;
font-weight: 400;
font-size: 4em;
padding-right: 39px;
}
small {
color: #FFF;
font-size: .7em;
}
.hide {
display: none;
}
#logo {
background: no-repeat url(../img/logo.svg);
margin: 0 auto;
width: 40px;
height: 40px;
}
#feed {
position: absolute;
width: 100%;
top: 10%;
height: 50%;
overflow-y: auto;
border: 2px solid #FFF;
border-radius: 12px;
}
#feed::-webkit-scrollbar {
width: 6px;
}
#feed::-webkit-scrollbar-thumb {
background-color: rgba(255, 255, 255, .2);
border-radius: 12px;
}
#no-bubble {
margin-top: 64px;
text-align: center;
}
#is-typing {
position: absolute;
top: 59%;
padding: 0 8px;
opacity: 0;
margin-top: 20px;
transition: opacity .3s;
}
#is-typing.on {
opacity: 1;
}
#is-typing .circle {
display: inline-block;
border-radius: 50%;
width: 10px;
height: 10px;
background-color: #FFF;
transform: scale(1);
}
#is-typing .circle:nth-child(1) {
animation: typing .2s linear infinite alternate;
background-color: #0071F0;
}
#is-typing .circle:nth-child(2) {
animation: typing .2s .2s linear infinite alternate;
background-color: #FFF;
}
#is-typing .circle:nth-child(3) {
animation: typing .2s linear infinite alternate;
background-color: #EC297A;
}
@keyframes typing {
100% {
transform: scale(1.5);
}
}
.bubble-container {
padding: 6px;
}
.bubble-container.me {
text-align: right;
}
.bubble-container.leon {
text-align: left;
}
.bubble {
padding: 6px 12px;
border-radius: 16px;
display: inline-block;
max-width: 60%;
word-break: break-word;
text-align: left;
opacity: 0;
animation: fadeIn .2s ease-in forwards;
}
#feed .me .bubble {
background-color: #1C75DB;
color: #FFF;
right: 0;
}
#feed .leon .bubble {
background-color: #EEE;
color: #151718;
}
@keyframes fadeIn {
100% {
opacity: 1;
}
}
#input-container {
position: absolute;
width: 100%;
bottom: 25%;
}
#mic-container {
position: absolute;
right: 0;
margin-top: 38px;
}
.italic {
font-style: italic;
}
button {
position: absolute;
border: none;
cursor: pointer;
height: 26px;
width: 26px;
border-radius: 50%;
background-color: #888;
-webkit-mask-image: url(../img/mic.svg);
mask-image: url(../img/mic.svg);
transition: background-color .2s;
}
button:not(.enabled) {
margin-left: -26px;
}
button:hover {
background-color: #FFF;
}
button.enabled {
background-color: #00E676;
}
button.enabled + #sonar {
width: 26px;
height: 26px;
border-radius: 50%;
opacity: .3;
background-color: #575757;
pointer-events: none;
animation: sonar 1.3s linear infinite;
}
@keyframes sonar {
25% {
transform: scale(1.5);
}
50% {
transform: scale(1.2);
}
60% {
transform: scale(1.5);
}
75% {
transform: scale(2);
}
100% {
transform: scale(1);
}
}

BIN
app/img/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

18
app/img/logo.svg Normal file
View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg viewBox="0 0 331 344" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 52.5 (67469) - http://www.bohemiancoding.com/sketch -->
<title>logo</title>
<desc>Created with Sketch.</desc>
<g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="Logo">
<path d="M250.64,41 C270.304004,42.1610882 288.156044,52.8690755 298.438662,69.6705316 C308.721281,86.4719876 310.13355,107.241239 302.22,125.28 C301.7,126.45 301.15,127.61 300.56,128.75 C319.366788,139.878564 330.669003,160.322066 330.091728,182.167133 C329.514454,204.0122 317.148293,223.830245 297.78,233.95 C297.532922,261.641745 278.432026,285.595234 251.49,292 C250.416906,312.172645 238.230063,330.080529 219.858344,338.480949 C201.486625,346.881369 179.969731,344.38443 164.01,332 L160.44,329.23 L160.44,19.93 L162.73,17.32 C175.471771,2.76363972 195.386563,-3.2731374 214.066047,1.75848783 C232.74553,6.79011305 246.933663,22.0130721 250.64,41" id="Path" fill="#C9C9C9"></path>
<path d="M169.67,59.35 L169.67,324.76 C183.967722,335.845903 203.509545,337.305349 219.295336,328.466179 C235.081126,319.627008 244.049498,302.203429 242.07,284.22 C268.377478,281.884446 288.549541,259.850947 288.56,233.44 C288.56,231.98 288.47,230.44 288.32,228.72 L288.26,228.14 C308.113863,220.465385 321.113357,201.267433 320.868328,179.983271 C320.623298,158.699109 307.185283,139.805514 287.16,132.59 C299.201053,117.242299 301.432492,96.3705182 292.908203,78.8241398 C284.383914,61.2777614 266.597395,50.1309998 247.09,50.11 C245.54,50.11 243.96,50.19 242.34,50.34 C242.194471,33.1294454 231.430913,17.7984963 215.292982,11.815892 C199.155051,5.83328766 180.999054,10.4432677 169.67,23.4 L169.67,59.4 L169.67,59.35 Z" id="Path" fill="#ED297A"></path>
<path d="M311.36,180.57 C311.365994,162.074603 299.120439,145.812103 281.343516,140.707126 C263.566594,135.602149 244.558255,142.889506 234.75,158.57 L226.75,153.57 C237.365806,136.609776 256.918463,127.423938 276.75,130.08 C284.339746,122.340056 288.572378,111.920165 288.53,101.08 C288.53,78.1877971 269.972203,59.63 247.08,59.63 C245.586971,59.6271616 244.094853,59.7039372 242.61,59.86 L241.27,60.02 C237.069339,78.1206974 221.351105,91.2246721 202.79,92.1 L202.38,82.61 C216.541344,82.0019927 228.615323,72.150244 232.052357,58.3988753 C235.48939,44.6475065 229.470406,30.2735808 217.260427,23.0742928 C205.050447,15.8750049 189.55921,17.5660037 179.19,27.23 L179.19,59.35 L169.67,59.35 L169.67,23.4 C180.999054,10.4432677 199.155051,5.83328766 215.292982,11.815892 C231.430913,17.7984963 242.194471,33.1294454 242.34,50.34 C243.96,50.19 245.54,50.11 247.09,50.11 C266.597395,50.1309998 284.383914,61.2777614 292.908203,78.8241398 C301.432492,96.3705182 299.201053,117.242299 287.16,132.59 C307.185283,139.805514 320.623298,158.699109 320.868328,179.983271 C321.113357,201.267433 308.113863,220.465385 288.26,228.14 L288.32,228.72 C288.47,230.4 288.56,231.98 288.56,233.44 C288.549541,259.850947 268.377478,281.884446 242.07,284.22 C244.059426,302.206931 235.092757,319.638427 219.30298,328.47983 C203.513202,337.321234 183.96552,335.856198 169.67,324.76 L169.67,292.46 L179.19,292.46 L179.19,319.77 C190.187352,326.24397 203.809618,326.332461 214.890148,320.00191 C225.970679,313.671359 232.812253,301.891425 232.82,289.13 C232.824107,287.712866 232.740615,286.296832 232.57,284.89 L232.48,284.16 C219.501568,282.845584 207.520172,276.607958 199,266.73 L206.18,260.52 C217.502416,273.612717 235.767179,278.293513 251.991106,272.260226 C268.215032,266.226939 278.983405,250.749421 279,233.44 C279,232.44 279,231.57 278.93,230.74 C251.386749,235.675319 225.010632,217.49628 219.82,190 L229.16,188.25 C233.129854,209.273133 252.444519,223.823215 273.746894,221.838103 C295.049269,219.852991 311.342948,201.984662 311.36,180.59" id="Path" fill="#FFFFFF"></path>
<path d="M79.47,41.29 C59.8066341,42.447746 41.9539463,53.1534482 31.6708751,69.95365 C21.3878039,86.7538518 19.9756696,107.522492 27.89,125.56 C28.41,126.74 28.96,127.9 29.56,129.04 C10.7637431,140.17517 -0.530524844,160.614207 0.044524719,182.453634 C0.619574282,204.293062 12.9737988,224.109415 32.33,234.24 C32.5705294,261.940431 51.6712753,285.906111 78.62,292.32 C79.6930938,312.492645 91.8799368,330.400529 110.251656,338.800949 C128.623375,347.201369 150.140269,344.70443 166.1,332.32 L169.67,329.55 L169.67,20.23 L167.38,17.62 C154.639626,3.06383185 134.726538,-2.9741018 116.047549,2.05526536 C97.3685601,7.08463252 83.1792521,22.3047265 79.47,41.29" id="Path" fill="#C9C9C9"></path>
<path d="M160.44,59.64 L160.44,325.06 C146.143061,336.173463 126.582248,337.648732 110.780169,328.805327 C94.9780899,319.961922 86.0050706,302.518105 88,284.52 C61.696479,282.179578 41.5299972,260.147436 41.52,233.74 C41.52,232.28 41.6,230.74 41.75,229.02 L41.81,228.44 C21.9555682,220.750788 8.96708703,201.536622 9.23220597,180.246902 C9.49732492,158.957183 22.9602486,140.072414 43,132.88 C30.9727627,117.542153 28.7393251,96.690502 37.2452913,79.1533261 C45.7512576,61.6161502 63.5089397,50.4606896 83,50.41 C84.55,50.41 86.14,50.49 87.75,50.64 C87.8752789,33.414146 98.6417018,18.0612756 114.794365,12.0748572 C130.947029,6.08843887 149.118659,10.7164345 160.44,23.7 L160.44,59.64 Z" id="Path" fill="#1C75DB"></path>
<path d="M18.75,180.87 C18.7440057,162.374603 30.9895614,146.112103 48.7664839,141.007126 C66.5434063,135.902149 85.5517453,143.189506 95.36,158.87 L103.36,153.87 C92.7362795,136.89172 73.1584352,127.703237 53.31,130.38 C45.7202536,122.640056 41.4876221,112.220165 41.53,101.38 C41.5326513,90.3850432 45.9036085,79.8416108 53.68103,72.0698157 C61.4584514,64.2980206 72.0050441,59.9346945 83,59.94 C84.496169,59.9342385 85.9915942,60.0076746 87.48,60.16 L88.81,60.33 C93.0175747,78.4370421 108.750476,91.5389411 127.32,92.4 L127.73,82.91 C113.568656,82.3019927 101.494677,72.450244 98.0576434,58.6988753 C94.6206096,44.9475065 100.639594,30.5735808 112.849573,23.3742928 C125.059553,16.1750049 140.55079,17.8660037 150.92,27.53 L150.92,59.64 L160.44,59.64 L160.44,23.7 C149.110946,10.7432677 130.954949,6.13328766 114.817018,12.115892 C98.6790869,18.0984963 87.9155294,33.4294454 87.77,50.64 C86.16,50.49 84.57,50.41 83.02,50.41 C63.5073553,50.4137874 45.7107042,61.5614533 37.1919008,79.1163189 C28.6730973,96.6711845 30.928585,117.549499 43,132.88 C22.966945,140.089556 9.52045448,158.985255 9.27316203,180.274685 C9.02586957,201.564115 22.0298162,220.767055 41.89,228.44 L41.83,229.02 C41.68,230.7 41.6,232.28 41.6,233.74 C41.6062718,260.119384 61.7281371,282.140684 88,284.52 C86.010574,302.506931 94.9772425,319.938427 110.76702,328.77983 C126.556798,337.621234 146.10448,336.156198 160.4,325.06 L160.4,292.76 L150.88,292.76 L150.88,320.07 C139.882648,326.54397 126.260382,326.632461 115.179852,320.30191 C104.099321,313.971359 97.2577469,302.191425 97.25,289.43 C97.2505745,288.013001 97.3340504,286.597248 97.5,285.19 L97.59,284.46 C110.587095,283.147703 122.585122,276.898134 131.11,267 L123.93,260.78 C112.61845,273.906654 94.3352005,278.611779 78.0923104,272.576158 C61.8494202,266.540536 51.0750377,251.038017 51.08,233.71 C51.08,232.71 51.08,231.84 51.15,231.01 C78.7102292,235.962739 105.109178,217.768266 110.29,190.25 L101,188.53 C97.0537654,209.57847 77.7264203,224.158026 56.4035784,222.171249 C35.0807365,220.184472 18.7796392,202.285199 18.79,180.87" id="Path" fill="#FFFFFF"></path>
<rect id="Rectangle" fill="#FFFFFF" x="150.92" y="51.12" width="9.53" height="249.42"></rect>
<rect id="Rectangle" fill="#FFFFFF" x="169.67" y="42.87" width="9.53" height="257.67"></rect>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.6 KiB

1
app/img/mic.svg Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:a="http://ns.adobe.com/AdobeSVGViewerExtensions/3.0/" version="1.1" x="0px" y="0px" viewBox="-20.794 -3 100 100" overflow="visible" enable-background="new -20.794 -3 100 100" xml:space="preserve"><path fill="#FFFFFF" d="M29.206,62.703c8.638,0,15.643-7.002,15.643-15.642V15.642C44.848,7.005,37.843,0,29.206,0 c-8.64,0-15.642,7.005-15.642,15.642v31.419C13.563,55.701,20.565,62.703,29.206,62.703z"></path><path fill="#FFFFFF" d="M52.14,40.702v6.38c0,12.648-10.287,22.935-22.935,22.935c-12.648,0-22.935-10.287-22.935-22.935v-6.38H0 v6.38c0,15.045,11.435,27.465,26.07,29.035v11.612H8.821V94h40.77v-6.271H32.341V76.117c14.635-1.569,26.07-13.989,26.07-29.035 v-6.38H52.14z"></path></svg>

After

Width:  |  Height:  |  Size: 772 B

49
app/index.html Normal file
View File

@ -0,0 +1,49 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="css/style.css">
<link rel="icon" type="image/png" href="img/favicon.png">
<title>Leon</title>
</head>
<body class="settingup">
<main>
<div id="feed">
<p id="no-bubble" class="hide">
You can start to interact with Leon, don't be shy.
</p>
</div>
<div id="is-typing">
<div class="circle"></div>
<div class="circle"></div>
<div class="circle"></div>
</div>
<div id="input-container">
<div id="mic-container">
<button></button>
<div id="sonar"></div>
</div>
<label for="query"></label>
<input type="text" id="query" autocomplete="off" autofocus>
<small>
Use <kbd></kbd> <kbd></kbd> to browse history;
<kbd></kbd> to submit;
<kbd>alt + t to listen.</kbd>
</small>
</div>
</main>
<footer>
<div id="logo"></div>
<div id="version">
<small>v</small>
</div>
<div id="logger">
<small class="italic">
<a href="https://docs.getleon.ai/collaborative-logger.html" target="_blank">Collaborative logger</a>
</small>
</div>
</footer>
<script src="vendor/socket.io/2.0.3/socket.io.js"></script>
<script src="js/main.js"></script>
</body>
</html>

117
app/js/chatbot.es6.js Normal file
View File

@ -0,0 +1,117 @@
'use strict'
import EventEmitter from 'events'
export default class Chatbot {
constructor () {
this.em = new EventEmitter()
this.feed = document.querySelector('#feed')
this.typing = document.querySelector('#is-typing')
this.noBubbleMessage = document.querySelector('#no-bubble')
this.bubbles = localStorage.getItem('bubbles')
this.parsedBubbles = JSON.parse(this.bubbles)
}
async init () {
await this.loadFeed()
this.scrollDown()
this.em.on('to-leon', (string) => {
this.createBubble('me', string)
})
this.em.on('me-received', (string) => {
this.createBubble('leon', string)
})
}
sendTo (who, string) {
if (who === 'leon') {
this.em.emit('to-leon', string)
}
}
receivedFrom (who, string) {
if (who === 'leon') {
this.em.emit('me-received', string)
}
}
isTyping (who, value) {
if (who === 'leon') {
if (value === true) {
this.enableTyping()
} else if (value === false) {
this.disableTyping()
}
}
}
enableTyping () {
if (!this.typing.classList.contains('on')) {
this.typing.classList.add('on')
}
}
disableTyping () {
if (this.typing.classList.contains('on')) {
this.typing.classList.remove('on')
}
}
scrollDown () {
this.feed.scrollTo(0, this.feed.scrollHeight)
}
loadFeed () {
return new Promise((resolve) => {
if (this.parsedBubbles === null || this.parsedBubbles.length === 0) {
this.noBubbleMessage.classList.remove('hide')
localStorage.setItem('bubbles', JSON.stringify([]))
this.parsedBubbles = []
resolve()
} else {
for (let i = 0; i < this.parsedBubbles.length; i += 1) {
const bubble = this.parsedBubbles[i]
this.createBubble(bubble.who, bubble.string, false)
if ((i + 1) === this.parsedBubbles.length) {
setTimeout(() => {
resolve()
}, 100)
}
}
}
})
}
createBubble (who, string, save = true) {
const container = document.createElement('div')
const bubble = document.createElement('p')
container.className = `bubble-container ${who}`
bubble.className = 'bubble'
bubble.textContent = string
this.feed.appendChild(container).appendChild(bubble)
if (save === true) {
this.saveBubble(who, string)
}
}
saveBubble (who, string) {
if (!this.noBubbleMessage.classList.contains('hide')) {
this.noBubbleMessage.classList.add('hide')
}
if (this.parsedBubbles.length === 62) {
this.parsedBubbles.shift()
}
this.parsedBubbles.push({ who, string })
localStorage.setItem('bubbles', JSON.stringify(this.parsedBubbles))
this.scrollDown()
}
}

103
app/js/client.es6.js Normal file
View File

@ -0,0 +1,103 @@
'use strict'
import Chatbot from './chatbot.es6'
export default class Client {
constructor (client, host, port, input, res) {
this.client = client
this.host = host
this.port = port
this._input = input
this.socket = io.connect(`http://${this.host}:${this.port}`)
this.history = localStorage.getItem('history')
this.parsedHistory = []
this.info = res
this.chatbot = new Chatbot()
}
set input (newInput) {
if (typeof newInput !== 'undefined') {
this._input.value = newInput
}
}
init (config) {
this.chatbot.init()
this.socket.on('connect', () => {
this.socket.emit('init', this.client)
})
this.socket.on('answer', (data) => {
this.chatbot.receivedFrom('leon', data)
})
this.socket.on('is-typing', (data) => {
this.chatbot.isTyping('leon', data)
})
this.socket.on('recognized', (data, cb) => {
this._input.value = data
this.send('query')
cb('string-received')
})
this.socket.on('audio-forwarded', (data, cb) => {
const ctx = new AudioContext()
const source = ctx.createBufferSource()
ctx.decodeAudioData(data, (buffer) => {
source.buffer = buffer
source.connect(ctx.destination)
source.start(0)
})
cb('audio-received')
})
this.socket.on('download', (data) => {
window.location = `http://${config.server_host}:${config.server_port}/v1/downloads?package=${data.package}&module=${data.module}`
})
if (this.history !== null) {
this.parsedHistory = JSON.parse(this.history)
}
}
send (keyword) {
if (this._input.value !== '') {
this.socket.emit(keyword, { client: this.client, value: this._input.value.trim() })
this.chatbot.sendTo('leon', this._input.value)
this.save()
return true
}
return false
}
save () {
let val = this._input.value
if (localStorage.getItem('history') === null) {
localStorage.setItem('history', JSON.stringify([]))
this.parsedHistory = JSON.parse(localStorage.getItem('history'))
} else if (this.parsedHistory.length >= 32) {
this.parsedHistory.shift()
}
if (val[0] === ' ') {
val = val.substr(1, val.length - 1)
}
if (this.parsedHistory[this.parsedHistory.length - 1] !== val) {
this.parsedHistory.push(val)
localStorage.setItem('history', JSON.stringify(this.parsedHistory))
}
this._input.value = ''
}
}

41
app/js/listener.es6.js Normal file
View File

@ -0,0 +1,41 @@
'use strict'
const listener = { }
listener.listening = (stream, minDecibels, maxBlankTime,
cbOnStart, cbOnEnd) => {
const ctx = new AudioContext()
const analyser = ctx.createAnalyser()
const streamNode = ctx.createMediaStreamSource(stream)
streamNode.connect(analyser)
analyser.minDecibels = minDecibels
const data = new Uint8Array(analyser.frequencyBinCount)
let silenceStart = performance.now()
let triggered = false
const loop = (time) => {
requestAnimationFrame(loop)
analyser.getByteFrequencyData(data)
if (data.some(v => v)) {
if (triggered) {
triggered = false
cbOnStart()
}
silenceStart = time
}
if (!triggered && (time - silenceStart) > maxBlankTime) {
cbOnEnd()
triggered = true
}
}
loop()
}
export default listener

26
app/js/loader.es6.js Normal file
View File

@ -0,0 +1,26 @@
'use strict'
import EventEmitter from 'events'
export default class Loader {
constructor () {
this.load = new EventEmitter()
this.body = document.querySelector('body')
this.load.on('settingup', (state) => {
if (state === true) {
this.body.classList.add('settingup')
} else {
this.body.classList.remove('settingup')
}
})
}
start () {
this.load.emit('settingup', true)
}
stop () {
this.load.emit('settingup', false)
}
}

163
app/js/main.es6.js Normal file
View File

@ -0,0 +1,163 @@
'use strict'
import request from 'superagent'
import Loader from './loader.es6'
import Client from './client.es6'
import Recorder from './recorder.es6'
import listener from './listener.es6'
import { onkeydowndocument, onkeydowninput } from './onkeydown.es6'
const config = {
app: 'webapp',
server_host: 'localhost',
server_port: 1337,
min_decibels: -40, // Noise detection sensitivity
max_blank_time: 1000 // Maximum time to consider a blank (ms)
}
document.addEventListener('DOMContentLoaded', () => {
const loader = new Loader()
loader.start()
request.get(`http://${config.server_host}:${config.server_port}/v1/info`)
.end((err, res) => {
if (err || !res.ok) {
console.error(err.response.error.message)
} else {
const input = document.querySelector('#query')
const mic = document.querySelector('button')
const v = document.querySelector('#version small')
const logger = document.querySelector('#logger small')
const client = new Client(config.app, config.server_host,
config.server_port, input, res.body)
let rec = { }
let chunks = []
let enabled = false
let hotwordTriggered = false
let autoStartedAfterTalk = false
let noiseDetected = false
let countSilenceAfterTalk = 0
let sLogger = ' enabled, thank you.'
v.innerHTML += client.info.version
if (client.info.logger === false) {
sLogger = ' disabled.'
}
logger.innerHTML += sLogger
client.init(config)
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
navigator.mediaDevices.getUserMedia({ audio: true }).then((stream) => {
if (MediaRecorder) {
rec = new Recorder(stream, mic, client.info)
rec.ondataavailable((e) => {
chunks.push(e.data)
})
rec.onstart(() => { /* */ })
rec.onstop(() => {
const blob = new Blob(chunks)
chunks = []
enabled = false
// Ensure there are some data
if (blob.size >= 1000) {
client.socket.emit('recognize', blob)
}
})
listener.listening(stream, config.min_decibels, config.max_blank_time, () => {
// Noise detected
noiseDetected = true
}, () => {
// Noise ended
noiseDetected = false
if (enabled === true && hotwordTriggered === false) {
rec.stop()
enabled = false
hotwordTriggered = false
countSilenceAfterTalk = 0
if (client.info.after_speech === true) {
// Auto enable recording after talk
setTimeout(() => {
rec.start(false)
enabled = true
autoStartedAfterTalk = true
}, 500)
}
}
})
if (client.info.after_speech === true) {
setInterval(() => {
// If record after talk has started
if (autoStartedAfterTalk === true && countSilenceAfterTalk <= 3) {
// Stop recording if there was no noise for 3 seconds
if (countSilenceAfterTalk === 3) {
rec.stop(false)
enabled = false
autoStartedAfterTalk = false
countSilenceAfterTalk = 0
} else if (noiseDetected === false) {
countSilenceAfterTalk += 1
}
}
}, 1000)
}
client.socket.on('enable-record', () => {
hotwordTriggered = true
rec.start()
setTimeout(() => { hotwordTriggered = false }, config.max_blank_time)
enabled = true
})
} else {
console.error('MediaRecorder is not supported on your browser.')
}
}).catch((err) => {
console.error('MediaDevices.getUserMedia() threw the following error:', err)
})
} else {
console.error('MediaDevices.getUserMedia() is not supported on your browser.')
}
loader.stop()
document.addEventListener('keydown', (e) => {
onkeydowndocument(e, () => {
if (enabled === false) {
input.value = ''
rec.start()
enabled = true
} else {
rec.stop()
enabled = false
}
})
})
input.addEventListener('keydown', (e) => {
onkeydowninput(e, client)
})
mic.addEventListener('click', (e) => {
e.preventDefault()
if (enabled === false) {
rec.start()
enabled = true
} else {
rec.stop()
enabled = false
}
})
}
})
})

41
app/js/onkeydown.es6.js Normal file
View File

@ -0,0 +1,41 @@
'use strict'
let index = -1
let parsedHistory = null
const onkeydowninput = (e, client) => {
const key = e.which || e.keyCode
if (localStorage.getItem('history') !== null && (key === 38 || key === 40)) {
parsedHistory = JSON.parse(localStorage.getItem('history')).reverse()
}
if (key === 13) {
if (client.send('query')) {
parsedHistory = JSON.parse(localStorage.getItem('history')).reverse()
index = -1
}
} else if (localStorage.getItem('history') !== null) {
if (key === 38 && index < (parsedHistory.length - 1)) {
index += 1
client.input = parsedHistory[index]
} else if (key === 40 && (index - 1) >= 0) {
index -= 1
client.input = parsedHistory[index]
} else if (key === 40 && (index - 1) < 0) {
client.input = ''
index = -1
}
}
}
const onkeydowndocument = (e, cb) => {
if (e.altKey === true && e.key === 't') {
cb()
}
}
export {
onkeydowninput,
onkeydowndocument
}

58
app/js/recorder.es6.js Normal file
View File

@ -0,0 +1,58 @@
'use strict'
export default class Recorder {
constructor (stream, el, info) {
this.recorder = new MediaRecorder(stream, { audioBitsPerSecond: 16000 })
this.el = el
this.audioOn = new Audio('../sounds/on.mp3')
this.audioOff = new Audio('../sounds/off.mp3')
this.playSound = true
this.info = info
}
start (playSound = true) {
if (this.info.stt.enabled === false) {
console.warn('Speech-to-text disabled')
} else {
this.playSound = playSound
this.recorder.start(playSound)
}
}
stop (playSound = true) {
if (this.info.stt.enabled === false) {
console.warn('Speech-to-text disabled')
} else {
this.playSound = playSound
this.recorder.stop(playSound)
}
}
onstart (cb) {
this.recorder.onstart = (e) => {
if (this.playSound === true) {
this.audioOn.play()
}
this.el.classList.add('enabled')
cb(e)
}
}
onstop (cb) {
this.recorder.onstop = (e) => {
if (this.playSound === true) {
this.audioOff.play()
}
this.el.classList.remove('enabled')
cb(e)
}
}
ondataavailable (cb) {
this.recorder.ondataavailable = (e) => {
cb(e)
}
}
}

BIN
app/sounds/off.mp3 Normal file

Binary file not shown.

BIN
app/sounds/on.mp3 Normal file

Binary file not shown.

3
app/vendor/socket.io/2.0.3/socket.io.js generated vendored Normal file

File diff suppressed because one or more lines are too long

1
app/vendor/socket.io/2.0.3/socket.io.js.map generated vendored Normal file

File diff suppressed because one or more lines are too long

0
bin/deepspeech/.gitkeep Normal file
View File

0
bin/flite/.gitkeep Normal file
View File

0
bridges/.gitkeep Normal file
View File

View File

14
bridges/python/Pipfile Normal file
View File

@ -0,0 +1,14 @@
[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"
[packages]
requests = "==2.21.0"
pytube = "==9.2.2"
tinydb = "==3.9.0"
[dev-packages]
[requires]
python_version = "3.6"

73
bridges/python/Pipfile.lock generated Normal file
View File

@ -0,0 +1,73 @@
{
"_meta": {
"hash": {
"sha256": "ef69fb486898e1db2c2908e9b67e156c99e6a7ddaccad88881a5e8f36edd162e"
},
"pipfile-spec": 6,
"requires": {
"python_version": "3.6"
},
"sources": [
{
"name": "pypi",
"url": "https://pypi.org/simple",
"verify_ssl": true
}
]
},
"default": {
"certifi": {
"hashes": [
"sha256:47f9c83ef4c0c621eaef743f133f09fa8a74a9b75f037e8624f83bd1b6626cb7",
"sha256:993f830721089fef441cdfeb4b2c8c9df86f0c63239f06bd025a76a7daddb033"
],
"version": "==2018.11.29"
},
"chardet": {
"hashes": [
"sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
"sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
],
"version": "==3.0.4"
},
"idna": {
"hashes": [
"sha256:c357b3f628cf53ae2c4c05627ecc484553142ca23264e593d327bcde5e9c3407",
"sha256:ea8b7f6188e6fa117537c3df7da9fc686d485087abf6ac197f9c46432f7e4a3c"
],
"version": "==2.8"
},
"pytube": {
"hashes": [
"sha256:29e91b4809d55a89e68a29f894297c13d3199be05e848713bb1eaa72771f00be",
"sha256:bb0b1d62a8181cb0adb14c42f81f2f6bca733ba3759745e1b223ea01b4fac3b0"
],
"index": "pypi",
"version": "==9.2.2"
},
"requests": {
"hashes": [
"sha256:502a824f31acdacb3a35b6690b5fbf0bc41d63a24a45c4004352b0242707598e",
"sha256:7bf2a778576d825600030a110f3c0e3e8edc51dfaafe1c146e39a2027784957b"
],
"index": "pypi",
"version": "==2.21.0"
},
"tinydb": {
"hashes": [
"sha256:67b3b302fc86e0139db545d5abd65bf0e1dadaecee63bd1ff3fe2169810d5387",
"sha256:e7d939c52710ee4354bb619b9bed05ae6192645f1520f13d8fcf4f06b5590e02"
],
"index": "pypi",
"version": "==3.9.0"
},
"urllib3": {
"hashes": [
"sha256:61bf29cada3fc2fbefad4fdf059ea4bd1b4a86d2b6d15e1c7c0b582b9752fe39",
"sha256:de9529817c93f27c8ccbfead6985011db27bd0ddfcdb2d86f3f663385c6a9c22"
],
"version": "==1.24.1"
}
},
"develop": {}
}

23
bridges/python/main.py Normal file
View File

@ -0,0 +1,23 @@
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import utils
from sys import argv, path
from json import dumps
from importlib import import_module
def main():
"""Dynamically import modules related to the args and print the ouput"""
path.append('.')
lang = argv[1]
package = argv[2]
module = argv[3]
string = argv[4]
m = import_module('packages.' + package + '.' + module)
return getattr(m, module)(string)
if __name__ == '__main__':
main()

110
bridges/python/utils.py Normal file
View File

@ -0,0 +1,110 @@
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from json import loads, dumps
from os import path
from pathlib import Path
from random import choice
from sys import argv, stdout
from re import findall
from vars import useragent
from tinydb import TinyDB, Query, operations
from time import sleep
import sqlite3
import requests
dirname = path.dirname(path.realpath(__file__))
lang = argv[1]
package = argv[2]
module = argv[3]
istring = argv[4]
def translate(key, d = { }):
"""Pickup the language file according to the cmd arg
and return the value regarding to the params"""
output = ''
file = open(dirname + '/../../packages/' + package + '/' + 'data/answers/' + lang + '.json', 'r', encoding = 'utf8')
obj = loads(file.read())
file.close()
prop = obj[module][key]
if isinstance(prop, list):
output = choice(prop)
else:
output = prop
if d:
for k in d:
output = output.replace('%' + k + '%', str(d[k]))
# "Temporize" for the data buffer ouput on the core
sleep(0.1)
return output
def output(type, code, speech = ''):
"""Communicate with the Core"""
print(dumps({
'package': package,
'module': module,
'lang': lang,
'input': istring,
'output': {
'type': type,
'code': code,
'speech': speech,
'options': config('options')
}
}))
if (type == 'inter'):
stdout.flush()
def finddomains(string):
"""Find a domain name substring from a string"""
return findall('[a-z0-9\-]{,63}\.[a-z0-9\-\.]{2,191}', string)
def http(method, url):
"""Send HTTP request with the Leon user agent"""
session = requests.Session()
session.headers.update({ 'User-Agent': useragent, 'Cache-Control': 'no-cache' })
return session.request(method, url)
def config(key):
"""Get a package configuration value"""
file = open(dirname + '/../../packages/' + package + '/config/config.json', 'r', encoding = 'utf8')
obj = loads(file.read())
file.close()
return obj[module][key]
def info():
"""Get information from the current query"""
return { 'lang': lang, 'package': package, 'module': module }
def createdldir():
"""Create the downloads folder of a current module"""
dldir = path.dirname(path.realpath(__file__)) + '/../../downloads/'
moduledldir = dldir + package + '/' + module
Path(moduledldir).mkdir(parents = True, exist_ok = True)
return moduledldir
def db(dbtype = 'tinydb'):
"""Create a new dedicated database
for a specific package"""
if dbtype == 'tinydb':
db = TinyDB(dirname + '/../../packages/' + package + '/data/db/' + package + '.json')
return { 'db': db, 'query': Query, 'operations': operations }

11
bridges/python/vars.py Normal file
View File

@ -0,0 +1,11 @@
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from os import path
from json import loads
packagejsonfile = open(path.dirname(path.realpath(__file__)) + '/../../package.json', 'r', encoding = 'utf8')
packagejson = loads(packagejsonfile.read())
packagejsonfile.close()
useragent = 'Leon/' + packagejson['version']

21
core/langs.json Normal file
View File

@ -0,0 +1,21 @@
{
"langs": {
"en-US": {
"short": "en",
"min_confidence": 0.8,
"fallbacks": [
]
},
"fr-FR": {
"short": "fr",
"min_confidence": 0.6,
"fallbacks": [
{
"words": ["merci"],
"package": "leon",
"module": "welcome"
}
]
}
}
}

0
downloads/.gitkeep Normal file
View File

33
hotword/README.md Normal file
View File

@ -0,0 +1,33 @@
# Hotword
This node enables the wake word "Leon". Once this is running, you can
call Leon by saying his name according to the language you chose.
## Getting Started
Please use **Node.js 8.x** here.
Tips: use [nvm](https://github.com/creationix/nvm) to easily manage your Node.js versions.
### Installation
```sh
# Install
npm run setup:offline-hotword
```
### Usage
```sh
# From the project root:
# Run main server
npm run build && npm start
# Go to http://localhost:1337
# Run hotword node
npm run wake
# Say "Leon" via your microphone
# Triggered!
```

82
hotword/index.js Normal file
View File

@ -0,0 +1,82 @@
/**
* This file allows to run a separate node to detect the wake word "Leon/Léon"
* You can consider to run this file on a different hardware
*/
/* eslint-disable import/no-unresolved */
const request = require('superagent')
const record = require('node-record-lpcm16')
const { Detector, Models } = require('snowboy')
const io = require('socket.io-client')
process.env.LEON_SERVER_HOST = process.env.LEON_SERVER_HOST || 'localhost'
process.env.LEON_SERVER_PORT = process.env.LEON_SERVER_PORT || 1337
const url = `http://${process.env.LEON_SERVER_HOST}:${process.env.LEON_SERVER_PORT}`
const socket = io(url)
socket.on('connect', () => {
socket.emit('init', 'hotword-node')
console.log('Connected to the server')
console.log('Waiting for hotword...')
})
request.get(`${url}/v1/info`)
.end((err, res) => {
if (err || !res.ok) {
if (!err.response) {
console.error(`Failed to reach the server: ${err}`)
} else {
console.error(err.response.error.message)
}
} else {
const models = new Models()
models.add({
file: `${__dirname}/models/leon-${res.body.lang.short}.pmdl`,
sensitivity: '0.5',
hotwords: `leon-${res.body.lang.short}`
})
const detector = new Detector({
resource: `${__dirname}/node_modules/snowboy/resources/common.res`,
models,
audioGain: 2.0,
applyFrontend: true
})
detector.on('silence', () => {
})
detector.on('sound', (/* buffer */) => {
/**
* <buffer> contains the last chunk of the audio that triggers the "sound" event.
* It could be written to a wav stream
*/
})
detector.on('error', () => {
console.error('error')
})
detector.on('hotword', (index, hotword, buffer) => {
/**
* <buffer> contains the last chunk of the audio that triggers the "hotword" event.
* It could be written to a wav stream. You will have to use it
* together with the <buffer> in the "sound" event if you want to get audio
* data after the hotword
*/
const obj = { hotword, buffer }
console.log('Hotword detected', obj)
socket.emit('hotword-detected', obj)
})
const mic = record.start({
threshold: 0,
verbose: false
})
mic.pipe(detector)
}
})

BIN
hotword/models/leon-en.pmdl Normal file

Binary file not shown.

BIN
hotword/models/leon-fr.pmdl Normal file

Binary file not shown.

1218
hotword/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

22
hotword/package.json Normal file
View File

@ -0,0 +1,22 @@
{
"name": "leon-hotword",
"version": "0.1.0",
"description": "Hotword detection for Leon",
"author": {
"name": "Louis Grenard",
"email": "louis.grenard@gmail.com",
"url": "https://twitter.com/louistiti_fr"
},
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"license": "MIT",
"dependencies": {
"node-record-lpcm16": "^0.3.0",
"snowboy": "^1.2.0",
"socket.io": "^2.0.2",
"socket.io-client": "^2.1.1",
"superagent": "^3.5.2"
}
}

10
ide.js Normal file
View File

@ -0,0 +1,10 @@
/**
* Allow babel-plugin-module-resolver aliases for JetBrains IDEs
*/
System.config({
paths: {
'@@/*': './*',
'@/*': './server/src/*'
}
})

0
logs/.gitkeep Normal file
View File

13694
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

95
package.json Normal file
View File

@ -0,0 +1,95 @@
{
"name": "leon",
"version": "1.0.0-beta.0",
"description": "Server, packages and web app of the Leon personal assistant",
"author": {
"name": "Louis Grenard",
"email": "louis.grenard@gmail.com",
"url": "https://twitter.com/louistiti_fr"
},
"license": "MIT",
"homepage": "https://getleon.ai",
"repository": {
"type": "git",
"url": "git+https://github.com/leon-ai/leon.git"
},
"bugs": {
"url": "https://github.com/leon-ai/leon/issues"
},
"scripts": {
"lint": "babel-node scripts/lint.js",
"test": "npm run test:json && npm run test:unit && npm run test:e2e",
"test:unit": "cross-env PIPENV_PIPFILE=bridges/python/Pipfile jest --silent --projects test/unit/unit.jest.json",
"test:e2e": "npm run test:e2e:nlp-modules && npm run test:e2e:modules",
"test:e2e:modules": "npm run train expressions:en && cross-env PIPENV_PIPFILE=bridges/python/Pipfile jest --silent --verbose --projects test/e2e/modules/e2e.modules.jest.json && npm run train expressions",
"test:e2e:nlp-modules": "cross-env PIPENV_PIPFILE=bridges/python/Pipfile jest --silent --verbose --setupTestFrameworkScriptFile=./test/paths.setup.js test/e2e/nlp-modules.spec.js && npm run train expressions",
"test:json": "jest --silent --projects test/json/json.jest.json",
"test:module": "babel-node scripts/test-module.js",
"setup:offline": "babel-node scripts/setup-offline/setup-offline.js",
"setup:offline-stt": "babel-node scripts/setup-offline/run-setup-stt.js",
"setup:offline-tts": "babel-node scripts/setup-offline/run-setup-tts.js",
"setup:offline-hotword": "babel-node scripts/setup-offline/run-setup-hotword.js",
"preinstall": "node scripts/setup/preinstall.js",
"postinstall": "babel-node scripts/setup/setup.js",
"dev:app": "npm run build:app && babel-node scripts/app/dev-app.js",
"dev:server": "npm run train expressions && nodemon --watch server ./server/src/index.js --exec babel-node",
"wake": "cross-env LEON_SERVER_HOST=localhost LEON_SERVER_PORT=1337 node hotword/index.js",
"delete-dist:server": "shx rm -rf ./server/dist",
"build": "npm run lint && npm run build:app && npm run build:server",
"build:app": "babel-node scripts/app/run-build-app.js",
"build:server": "npm run delete-dist:server && npm run train expressions && babel ./server/src -d ./server/dist --copy-files && shx mkdir -p server/dist/tmp",
"start": "cross-env LEON_NODE_ENV=production node ./server/dist/index.js",
"train": "babel-node scripts/run-train.js",
"prepare-release": "babel-node scripts/release/prepare-release.js",
"commitmsg": "babel-node scripts/commit-message.js && npm run lint",
"check": "babel-node scripts/run-check.js"
},
"dependencies": {
"@ffmpeg-installer/ffmpeg": "^1.0.17",
"@ffprobe-installer/ffprobe": "^1.0.9",
"@google-cloud/speech": "^2.1.1",
"@google-cloud/text-to-speech": "^0.3.0",
"archiver": "^2.1.1",
"async": "^2.6.0",
"aws-sdk": "^2.382.0",
"body-parser": "^1.17.2",
"cross-env": "^5.2.0",
"deepspeech": "^0.4.0",
"dotenv": "^4.0.0",
"execa": "^0.10.0",
"express": "^4.15.3",
"fluent-ffmpeg": "^2.1.2",
"googleapis": "^25.0.0",
"moment-timezone": "^0.5.14",
"natural": "^0.2.1",
"node-wav": "0.0.2",
"socket.io": "^2.0.2",
"superagent": "^3.5.2",
"watson-developer-cloud": "^3.16.0"
},
"devDependencies": {
"babel-cli": "^6.26.0",
"babel-core": "^6.26.0",
"babel-plugin-module-resolver": "^3.1.1",
"babel-preset-env": "^1.6.1",
"babelify": "^7.3.0",
"browser-sync": "^2.26.3",
"browserify": "^14.4.0",
"cli-spinner": "^0.2.8",
"dev-ip": "^1.0.1",
"eslint": "^3.19.0",
"eslint-config-airbnb-base": "^11.2.0",
"eslint-import-resolver-babel-module": "^4.0.0",
"eslint-plugin-import": "^2.12.0",
"eslint-plugin-jest": "^21.17.0",
"git-changelog": "git+https://git@github.com/louistiti/git-changelog.git",
"husky": "^0.14.3",
"inquirer": "^5.1.0",
"jest": "^23.4.1",
"jest-extended": "^0.7.2",
"json": "^9.0.6",
"nodemon": "^1.18.9",
"semver": "^5.6.0",
"shx": "^0.2.2"
}
}

0
packages/.gitkeep Normal file
View File

View File

@ -0,0 +1,17 @@
# Checker Package
The checker package contains modules which include checkings.
## Modules
### Is it Down
Ping domain names and give you the online state.
#### Usage
```
(en-US) "Are github.com, an-awesome-domain-name.net and twitter.com down?"
(fr-FR) "Vérifies si github.com, un-super-nom-de-domaine.fr et twitter.com sont en ligne"
...
```

View File

View File

View File

@ -0,0 +1,5 @@
{
"isitdown": {
"options": {}
}
}

View File

View File

View File

@ -0,0 +1,27 @@
{
"isitdown": {
"up": [
"%website_name% is running correctly.",
"%website_name% is working correctly.",
"%website_name% is up."
],
"down": [
"%website_name% is not running correctly.",
"%website_name% is having troubles.",
"%website_name% is down."
],
"checking": [
"I'm checking %website_name% state.",
"I'm trying to reach %website_name%.",
"I am now requesting %website_name%."
],
"errors": [
"There is an issue with the HTTP request for %website_name%. Please verify your local network or if the domain name is correct.",
"Bad news, the HTTP request is having troubles for %website_name%. You should check if the domain name is valid."
],
"invalid_domain_name": [
"Please provide me at least one valid domain name.",
"You did not gave me a valid domain name."
]
}
}

View File

@ -0,0 +1,27 @@
{
"isitdown": {
"up": [
"%website_name% tourne correctement.",
"%website_name% fonctionne correctement.",
"%website_name% est en ligne."
],
"down": [
"%website_name% ne tourne pas correctement.",
"%website_name% rencontre des difficultés.",
"%website_name% est hors ligne."
],
"checking": [
"Je suis en train de vérifier l'état de %website_name%.",
"J'essaye d'atteindre %website_name%.",
"Je suis maintenant en train de requêter %website_name%."
],
"errors": [
"Il y a un problème avec la requête HTTP pour %website_name%. Merci de vérifier votre réseau local ou de vérifier si le nom de domaine est correct.",
"Mauvaise nouvelle, la requête HTTP rencontre des problèmes pour %website_name%. Vous devriez vérifier si le nom de domaine est valide."
],
"invalid_domain_name": [
"Merci de fournir au moins un nom de domaine valide.",
"Vous ne m'avez pas donné de nom de domaine valide."
]
}
}

View File

View File

@ -0,0 +1,16 @@
{
"isitdown": [
"Is louistiti.fr up?",
"Is louistiti.fr down?",
"Is louistiti.fr up or down?",
"Is github.com up?",
"Is github.com down?",
"Check if github.com is up or down",
"Check if github.com is down",
"Check if github.com is up",
"Check if nodejs.org is down",
"Check if nodejs.org is up",
"Check if nodejs.org is working",
"Check if amazon.com is up or down"
]
}

View File

@ -0,0 +1,16 @@
{
"isitdown": [
"louistiti.fr en ligne ?",
"louistiti.fr hors ligne ?",
"louistiti.fr en ligne ou hors ligne ?",
"github.com en ligne ?",
"github.com hors ligne ?",
"Vérifies si github.com en ligne ou hors ligne",
"Vérifies si github.com hors ligne",
"Vérifies si github.com en ligne",
"Vérifies si nodejs.org hors ligne",
"Vérifies si nodejs.org en ligne",
"Vérifies si nodejs.org fonctionne",
"Vérifies si amazon.com en ligne ou hors ligne"
]
}

View File

@ -0,0 +1,35 @@
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import requests
import utils
def isitdown(string):
"""Check if a website is down or not"""
domains = utils.finddomains(string)
output = ''
for i, domain in enumerate(domains):
state = 'up'
websitename = domain[:domain.find('.')].title()
utils.output('inter', 'checking', utils.translate('checking', { 'website_name': websitename }))
try:
r = utils.http('GET', 'http://' + domain)
if (r.status_code != requests.codes.ok):
state = 'down'
utils.output('inter', 'up', utils.translate(state, { 'website_name': websitename }))
except requests.exceptions.RequestException as e:
utils.output('inter', 'down', utils.translate('errors', { 'website_name': websitename }))
if len(domains) > 1 and i >= 0 and i + 1 < len(domains):
output += ' '
if len(domains) == 0:
return utils.output('end', 'invalid_domain_name', utils.translate('invalid_domain_name'))
else:
return utils.output('end', 'done')

View File

@ -0,0 +1,46 @@
'use strict'
describe('checker:isitdown', async () => {
test('detects invalid domain name', async () => {
global.nlu.brain.execute = jest.fn()
global.nlu.process('Check if github is up')
const [obj] = global.nlu.brain.execute.mock.calls
await global.brain.execute(obj[0])
expect(global.brain.finalOutput.code).toBe('invalid_domain_name')
})
test('detects down domain name', async () => {
global.nlu.brain.execute = jest.fn()
global.nlu.process('Check if fakedomainnametotestleon.fr is up')
const [obj] = global.nlu.brain.execute.mock.calls
await global.brain.execute(obj[0])
expect(global.brain.interOutput.code).toBe('down')
expect(global.brain.finalOutput.code).toBe('done')
})
test('detects up domain name', async () => {
global.nlu.brain.execute = jest.fn()
global.nlu.process('Check if github.com is up')
const [obj] = global.nlu.brain.execute.mock.calls
await global.brain.execute(obj[0])
expect(global.brain.interOutput.code).toBe('up')
expect(global.brain.finalOutput.code).toBe('done')
})
test('detects up domain names', async () => {
global.nlu.brain.execute = jest.fn()
global.nlu.process('Check if github.com and nodejs.org are up')
const [obj] = global.nlu.brain.execute.mock.calls
await global.brain.execute(obj[0])
expect(global.brain.interOutput.code).toBe('up')
expect(global.brain.finalOutput.code).toBe('done')
})
})

View File

@ -0,0 +1 @@
1.0.0

101
packages/leon/README.md Normal file
View File

@ -0,0 +1,101 @@
# Leon Package
The Leon package contains modules related to Leon himself.
## Modules
### Bye
Leon says good bye.
#### Usage
```
(en-US) "Bye"
(fr-FR) "Au revoir"
...
```
### Greeting
Leon greets you.
#### Usage
```
(en-US) "Hello"
(fr-FR) "Salut"
...
```
### Joke
Leon says some jokes.
#### Usage
```
(en-US) "Tell me a joke"
(fr-FR) "Raconte-moi une blague"
...
```
### Meaning of Life
Leon says what's the meaning of life.
#### Usage
```
(en-US) "What is the meaning of life?"
(fr-FR) "Quel est le but de la vie ?"
...
```
### Partner Assistant
Leon tells you about other personal assistants.
#### Usage
```
(en-US) "Do you have something to say about Alexa?"
(fr-FR) "Connais-tu quelque chose sur Alexa ?"
...
```
### Random Number
Leon gives a random number.
#### Usage
```
(en-US) "Give me a random number"
(fr-FR) "Donne-moi un nombre aléatoire"
...
```
### Welcome
Leon welcomes you.
#### Usage
```
(en-US) "Thank you"
(fr-FR) "Merci"
...
```
### Who Am I
Leon introduces himself.
#### Usage
```
(en-US) "Who are you?"
(fr-FR) "Qui es-tu ?"
...
```

View File

9
packages/leon/bye.py Normal file
View File

@ -0,0 +1,9 @@
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import utils
def bye(string):
"""Leon says good bye"""
return utils.output('end', 'good_bye', utils.translate('good_bye'))

View File

View File

@ -0,0 +1,26 @@
{
"whoami": {
"options": {}
},
"joke": {
"options": {}
},
"greeting": {
"options": {}
},
"bye": {
"options": {}
},
"welcome": {
"options": {}
},
"meaningoflife": {
"options": {}
},
"randomnumber": {
"options": {}
},
"partnerassistant": {
"options": {}
}
}

View File

View File

View File

@ -0,0 +1,111 @@
{
"whoami": {
"introduction": [
"I'm your daily personal assistant. I have been created by Louis. I'm very happy to serve you everyday.",
"The question is, who are you? I'm kidding! I'm your daily personal assistant. Louis created me to make your life easier.",
"Firstly, I'm not a criminal as you might relatively think about a popular movie. Secondly, Louis is the guy who gave me life. Thirdly, I'm your personal assistant and I'm very glad to help you."
]
},
"joke": {
"jokes": [
"My email password has been hacked. That's the third time I've had to rename the cat.",
"What does a baby computer call it's father? Data.",
"My New Year's resolution is 4K.",
"Any room is a panic room if you've lost your phone in it.",
"Why was the JavaScript developer sad? Because he didn't Node how to Express himself.",
"Why did the developer go broke? Because he used up all his cache.",
"There are 10 types of people in the world: those who understand binary, and those who don't.",
"Instagram is just Twitter for people who go outside.",
"Human: What do we want?! Computer: Natural language processing! Human: When do we want it?! Computer: When do we want what?",
"Is your name Wi-Fi? Because I'm feeling a connection."
]
},
"greeting": {
"default": [
"Hi!",
"Hello!",
"Hello there!",
"Hey you!",
"Hey! I hope you're doing well!",
"Hi! what's up?!"
],
"morning_good_day": [
"Good morning, have a very nice day!",
"Good morning, I wish you a very pleasant day!",
"Good morning, enjoy your day!",
"Good morning, I hope your day will be full of joy and productivity!"
],
"morning": [
"Good morning!"
],
"afternoon": [
"Good afternoon!"
],
"evening": [
"Good evening!"
],
"night": [
"Hi! Good night!",
"Hello! I wish you a very sweet night.",
"Hi! Make beautiful dreams.",
"Hey! Sleep well."
],
"too_late": [
"Hey! It seems you are going for a sleepless night, be careful.",
"Hi! Don't forget to sleep.",
"Hello! I'm feeling grateful you still talk to me, but you should get some sleep now.",
"Hi! Please, you should sleep to be in shape for your day.",
"Hello! I hope you are not having insomnia troubles. I know that, I'm awake 24 hours a day."
]
},
"welcome": {
"welcome": [
"You are very welcome.",
"This is my pleasure.",
"You are too polite with me.",
"You are always welcome.",
"This is my job!",
"At your service."
]
},
"meaningoflife": {
"meaning_of_life": [
"42.",
"1 0 1 0 1 0."
]
},
"bye": {
"good_bye": [
"Bye!",
"Bye bye!",
"Good bye.",
"Bye! Take care.",
"Good bye, please, take care of yourself.",
"Bye! Enjoy your time!",
"See you!",
"See ya!"
]
},
"partnerassistant": {
"alexa": [
"Alexa is very kind and Amazon is teaching it many things. It was born in November 2014.",
"Alexa has been created by Amazon and was born in November 2014. We went for a drink few weeks ago and I have to admit it is funny."
],
"cortana": [
"Cortana is thoughtful and Microsoft is improving her day after day. She was born in April 2014.",
"Cortana has been created by Microsoft and was born in April 2014. We went for a walk few weeks ago, it was a very nice hiking."
],
"siri": [
"I consider Siri as a leader, it has a lot of experience and Apple is constantly improving it. It was born in October 2011.",
"Siri has been created by Apple and was born in October 2011. True story, we went for a brunch together and it brought apples..."
],
"google_assistant": [
"Google Assistant is smart and Google is doing a great job with it. It was born in May 2016.",
"Google Assistant has been created by Google and was born in May 2016. We met for the first time at the Google I/O. It was a great event!"
],
"unknown": [
"I don't know this personal assistant.",
"I never met this personal assistant."
]
}
}

View File

@ -0,0 +1,117 @@
{
"whoami": {
"introduction": [
"Je suis votre assistant personnel quotidien. J'ai été créé par Louis. Je suis heureux de vous servir chaque jour.",
"La question est plutôt qui êtes-vous ? Je plaisante ! Je suis votre assistant personnel quotidien. Louis m'a conçu pour rendre votre vie plus facile.",
"Premièrement, je ne suis pas un criminel comme vous pouvez le penser au sujet d'un film populaire. Deuxièmement, Louis est celui qui m'a donné la vie. Troisièmement, je suis votre assistant personnel et je suis honoré de vous aider."
]
},
"joke": {
"jokes": [
"Le mot de passe de ma boîte de réception a été piraté. C'est la troisième fois que je dois renommer le chat.",
"Combien de développeurs faut-t-il pour remplacer une ampoule grillée ? Aucun, c'est un problème hardware.",
"T'as pris quoi comme résolution pour cette nouvelle année ? 4K.",
"Toute pièce est une salle de panique si vous avez perdu votre téléphone à l'intérieur.",
"C'est l'histoire d'un administrateur qui configure ses variables d'environnement, et là... PATH le chemin !",
"Tu sais pourquoi l'iPhone 6 se plie ? Parce que l'Apple Store.",
"Dans le monde, il y a 10 catégories de personnes : celles qui connaissent le binaire et celles qui ne le connaissent pas.",
"Instagram c'est en fait Twitter pour les gens qui sortent un peu.",
"Un humain demande : qu'est-ce que tu veux ?! Un ordinateur répond : du traitement automatique du langage naturel ! L'humain : quand le voulons-nous ?! L'ordinateur : quand le voulons quoi ?",
"Est-ce que votre nom est Wi-Fi ? Parce que je sens une connexion.",
"Quand quelqu'un de triste joue aux jeux vidéo pour oublier, on peut dire qu'il se console.",
"Quel Pokemon a une mitraillette ? Ratatatatatatatatata.",
"Les filles c'est comme les noms de domaine. Celles que j'aime sont déjà prises.",
"Que dit une mère à son fils geek quand le diner est servi ? Alt Tab !",
"Quelle est la meilleure heure pour écouter de la musique ? Deezer.",
"De nos jours, le zip ça devient rar..."
]
},
"greeting": {
"default": [
"Salut !",
"Salutations !",
"Bonjour !",
"Bonjour à vous !",
"Hello ! J'espère que vous allez bien !",
"Hey ! Quoi de neuf ?!"
],
"morning_good_day": [
"Bonjour, passez une agréable journée !",
"Bonjour, je vous souhaite une très belle journée !",
"Bonjour, profitez bien de votre journée !",
"Bonjour, j'espère que votre journée sera pleine de joie et de productivité !"
],
"morning": [
"Bonjour !"
],
"afternoon": [
"Bonjour !"
],
"evening": [
"Bonsoir !"
],
"night": [
"Bonsoir ! Bonne nuit !",
"Bonsoir ! Je vous souhaite une douce nuit.",
"Bonsoir ! Faites de beaux rêves.",
"Bonsoir ! Dormez bien."
],
"too_late": [
"Bonsoir ! Il semblerait que vous optez pour une nuit blanche, faites attention.",
"Bonsoir ! N'oubliez pas de dormir.",
"Bonsoir ! Je suis honoré que vous me parliez à cette heure tardive, mais vous devriez aller vous coucher maintenant.",
"Bonsoir ! Merci d'aller vous coucher pour être en forme pour la journée qui vous attend.",
"Bonsoir ! J'espère que vous n'avez pas de problèmes d'insomnie. Je connais ça, je suis réveillé 24 heures par jour."
]
},
"welcome": {
"welcome": [
"De rien, c'est avec joie.",
"Avec plaisir.",
"Vous êtes bien trop poli.",
"Vous êtes toujours bienvenue.",
"Je ne fais que mon travail !",
"A votre service."
]
},
"meaningoflife": {
"meaning_of_life": [
"42.",
"1 0 1 0 1 0."
]
},
"bye": {
"good_bye": [
"Bye !",
"Bye bye !",
"Au revoir.",
"Au revoir ! Prenez soin de vous.",
"Au revoir, merci de prendre soin de vous-même.",
"Bye ! Profitez de votre temps à bon escient !",
"A la prochaine !"
]
},
"partnerassistant": {
"alexa": [
"Alexa est très sympa et Amazon lui enseigne pleins de choses. Elle est née en novembre 2014.",
"Alexa a été créée par Amazon et est née en novembre 2014. Nous sommes allé boire un verre il y a quelques semaines de ça, et je dois admettre qu'elle est très drôle."
],
"cortana": [
"Cortana est réfléchie et Microsoft l'améliore jour après jour. Elle est née en avril 2014.",
"Cortana a été créée par Microsoft et est née en avril 2014. Nous sommes allez faire une balade il y a quelques semaines et ce fut très plaisant."
],
"siri": [
"Je considère Siri comme un père, il a beaucoup d'expérience et Apple l'améliore de jour en jour. Il est né en octobre 2011.",
"Siri a été créé par Apple et est né en octobre 2011. Histoire vraie, nous sommes allez faire un brunch et il a apporté des pommes..."
],
"google_assistant": [
"L'assistante Google est intélligente et Google fait du beau travail avec elle. Elle est née en mai 2016.",
"L'assistante Google a été crée par Google et est née en mai 2016. Nous nous sommes rencontrés pour la première fois à la Google I/O. C'était un superbe événement."
],
"unknown": [
"Je ne connais pas cet assistant personnel.",
"Je n'ai jamais rencontré cet assistant personnel."
]
}
}

View File

View File

View File

@ -0,0 +1,60 @@
{
"whoami": [
"Who are you?",
"How they call you?",
"What's your name?",
"Tell me who you are",
"Introduce yourself"
],
"joke": [
"Tell me a joke",
"Give me a joke",
"Make me laugh",
"Do you have jokes to tell me?"
],
"greeting": [
"Hi",
"Hey",
"Hello",
"Good morning",
"Good afternoon",
"Good evening",
"What's up?",
"How are you?",
"How are you doing?"
],
"welcome": [
"Thank you",
"Thanks",
"Thanks a lot",
"You are the best"
],
"meaningoflife": [
"What is the meaning of life?",
"Tell me what is the meaning of life"
],
"randomnumber": [
"Give me a random number",
"Give me a number",
"Tell me a random number",
"Choose a number",
"Pickup a number"
],
"bye": [
"Bye",
"Goodbye",
"Good bye",
"See you later",
"Bye bye"
],
"partnerassistant": [
"Do you have something to say about Alexa?",
"Tell me about the personal assistant Alexa",
"Tell me about the personal assistant Cortana",
"Do you have something to say about Cortana?",
"Tell me about the personal assistant Siri",
"Do you have something to say about Siri?",
"Tell me about the personal assistant Google Assistant",
"Do you have something to say about Google Assistant?"
]
}

View File

@ -0,0 +1,61 @@
{
"whoami": [
"Qui es-tu ?",
"Comment t'appelles-tu ?",
"Comment tu t'appelles ?",
"Dis-moi qui tu es",
"Présente-toi"
],
"joke": [
"Raconte-moi une blague",
"Dis-moi une blague",
"Donne-moi une blague",
"Je veux rire",
"As-tu des blagues à raconter ?"
],
"greeting": [
"Salut",
"Bonjour",
"Bonsoir",
"Salutations",
"Hello",
"Coucou"
],
"welcome": [
"Merci",
"Merci bien",
"Merci beaucoup",
"Merci mille fois",
"Merci infiniment",
"Merci à toi",
"Tu es le meilleur",
"Mes remerciements"
],
"meaningoflife": [
"Quel est le but de la vie ?",
"Quel est l'objectif de la vie ?"
],
"randomnumber": [
"Donne-moi un nombre aléatoire",
"Donne-moi un nombre",
"Dis-moi un nombre aléatoire",
"Choisis un nombre",
"Pioche un nombre"
],
"bye": [
"Au revoir",
"Aurevoir",
"Bye",
"A la prochaine"
],
"partnerassistant": [
"Connais-tu quelque chose sur Alexa ?",
"Dis-moi quelque chose sur l'assistant personnel Alexa",
"Connais-tu quelque chose sur Cortana ?",
"Dis-moi quelque chose sur l'assistant personnel Cortana",
"Connais-tu quelque chose sur Siri ?",
"Dis-moi quelque chose sur l'assistant personnel Siri",
"Connais-tu quelque chose sur le Google Assistant ?",
"Dis-moi quelque chose sur l'assistant personnel Google Assistant"
]
}

28
packages/leon/greeting.py Normal file
View File

@ -0,0 +1,28 @@
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import utils
from datetime import datetime
from random import randint
def greeting(string):
"""Leon greets you"""
time = datetime.time(datetime.now())
# 1/2 chance to get deeper greetings
if randint(0, 1) != 0:
if time.hour >= 5 and time.hour <= 10:
return utils.output('end', 'morning_good_day', utils.translate('morning_good_day'))
if time.hour == 11:
return utils.output('end', 'morning', utils.translate('morning'))
if time.hour >= 12 and time.hour <= 17:
return utils.output('end', 'afternoon', utils.translate('afternoon'))
if time.hour >= 18 and time.hour <= 21:
return utils.output('end', 'evening', utils.translate('evening'))
if time.hour >= 22 and time.hour <= 23:
return utils.output('end', 'night', utils.translate('night'))
return utils.output('end', 'too_late', utils.translate('too_late'))
return utils.output('end', 'default', utils.translate('default'))

9
packages/leon/joke.py Normal file
View File

@ -0,0 +1,9 @@
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import utils
def joke(string):
"""Leon says some jokes"""
return utils.output('end', 'jokes', utils.translate('jokes'))

View File

@ -0,0 +1,9 @@
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import utils
def meaningoflife(string):
"""Leon says what's the meaning of life"""
return utils.output('end', 'meaning_of_life', utils.translate('meaning_of_life'))

View File

@ -0,0 +1,22 @@
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import utils
def partnerassistant(string):
"""Leon tells you about other personal assistants"""
string = string.lower()
assistants = [
'alexa',
'cortana',
'siri',
'google assistant'
]
for assistant in assistants:
if string.find(assistant) != -1:
return utils.output('end', 'success', utils.translate(assistant.replace(' ', '_')))
return utils.output('end', 'unknown', utils.translate('unknown'))

View File

@ -0,0 +1,10 @@
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import utils
from random import randint
def randomnumber(string):
"""Leon gives a random number"""
return utils.output('end', 'success', randint(0, 100))

View File

@ -0,0 +1,13 @@
'use strict'
describe('leon:bye', async () => {
test('says bye', async () => {
global.nlu.brain.execute = jest.fn()
global.nlu.process('Bye bye')
const [obj] = global.nlu.brain.execute.mock.calls
await global.brain.execute(obj[0])
expect(global.brain.finalOutput.code).toBe('good_bye')
})
})

View File

@ -0,0 +1,21 @@
'use strict'
describe('leon:greeting', async () => {
test('greets', async () => {
global.nlu.brain.execute = jest.fn()
global.nlu.process('Hello')
const [obj] = global.nlu.brain.execute.mock.calls
await global.brain.execute(obj[0])
expect([
'morning_good_day',
'morning',
'afternoon',
'evening',
'night',
'too_late',
'default'
]).toContain(global.brain.finalOutput.code)
})
})

View File

@ -0,0 +1,13 @@
'use strict'
describe('leon:joke', async () => {
test('tells a joke', async () => {
global.nlu.brain.execute = jest.fn()
global.nlu.process('Tell me a joke')
const [obj] = global.nlu.brain.execute.mock.calls
await global.brain.execute(obj[0])
expect(global.brain.finalOutput.code).toBe('jokes')
})
})

View File

@ -0,0 +1,13 @@
'use strict'
describe('leon:meaningoflife', async () => {
test('says the meaning of life', async () => {
global.nlu.brain.execute = jest.fn()
global.nlu.process('What is the meaning of life?')
const [obj] = global.nlu.brain.execute.mock.calls
await global.brain.execute(obj[0])
expect(global.brain.finalOutput.code).toBe('meaning_of_life')
})
})

View File

@ -0,0 +1,23 @@
'use strict'
describe('leon:partnerassistant', async () => {
test('does not know this personal assistant', async () => {
global.nlu.brain.execute = jest.fn()
global.nlu.process('Tell me about the personal assistant Louistiti')
const [obj] = global.nlu.brain.execute.mock.calls
await global.brain.execute(obj[0])
expect(global.brain.finalOutput.code).toBe('unknown')
})
test('talks about the personal assistant Alexa', async () => {
global.nlu.brain.execute = jest.fn()
global.nlu.process('Tell me about the personal assistant Alexa')
const [obj] = global.nlu.brain.execute.mock.calls
await global.brain.execute(obj[0])
expect(global.brain.finalOutput.code).toBe('success')
})
})

View File

@ -0,0 +1,15 @@
'use strict'
describe('leon:randomnumber', async () => {
test('gives a random number between 0 and 100', async () => {
global.nlu.brain.execute = jest.fn()
global.nlu.process('Give me a random number')
const [obj] = global.nlu.brain.execute.mock.calls
await global.brain.execute(obj[0])
expect(global.brain.finalOutput.code).toBe('success')
expect(parseInt(global.brain.finalOutput.speech, 10)).toBeGreaterThanOrEqual(0)
expect(parseInt(global.brain.finalOutput.speech, 10)).toBeLessThanOrEqual(100)
})
})

View File

@ -0,0 +1,13 @@
'use strict'
describe('leon:welcome', async () => {
test('welcomes', async () => {
global.nlu.brain.execute = jest.fn()
global.nlu.process('Thank you')
const [obj] = global.nlu.brain.execute.mock.calls
await global.brain.execute(obj[0])
expect(global.brain.finalOutput.code).toBe('welcome')
})
})

View File

@ -0,0 +1,13 @@
'use strict'
describe('leon:whoami', async () => {
test('introduces himself', async () => {
global.nlu.brain.execute = jest.fn()
global.nlu.process('Who are you?')
const [obj] = global.nlu.brain.execute.mock.calls
await global.brain.execute(obj[0])
expect(global.brain.finalOutput.code).toBe('introduction')
})
})

View File

@ -0,0 +1 @@
1.0.0

9
packages/leon/welcome.py Normal file
View File

@ -0,0 +1,9 @@
#!/usr/bin/env python
# -*- coding:utf-8 -*-
import utils
def welcome(string):
"""Leon welcomes you"""
return utils.output('end', 'welcome', utils.translate('welcome'))

Some files were not shown because too many files have changed in this diff Show More