Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an API for plugins #14

Open
wants to merge 35 commits into
base: master-v2
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
30fa971
Get the API set up and remove unneeded `start` function
Akul2010 May 4, 2023
15a70ef
Update paths in API for simplicity
Akul2010 May 4, 2023
225dabc
Add fastapi to the dependencies
Akul2010 May 4, 2023
44bb959
Delete makefile as it was unneeded
Akul2010 May 9, 2023
a17a4c6
Update create_plugins.md to include code for API
Akul2010 May 10, 2023
ea16452
Use sys.exit() rather than exit()
Akul2010 May 11, 2023
e3c5ef5
Fixed API set up not working
Akul2010 May 24, 2023
e20e814
Automatically open the API link in browser
Akul2010 May 25, 2023
f4f2e5d
Merge branch 'Akul-AI:master-v2' into add-plugin-api-v2
Akul2010 May 25, 2023
12a9188
Merge branch 'Akul-AI:master-v2' into add-plugin-api-v2
Akul2010 May 26, 2023
9030204
Merge branch 'Akul-AI:master-v2' into add-plugin-api-v2
Akul2010 May 26, 2023
ef8c1e6
Merge branch 'Akul-AI:master-v2' into add-plugin-api-v2
Akul2010 Jun 10, 2023
8c01512
Replace `self` with `akulai` for objects outside of the `AkulAI` class
Akul2010 Jun 10, 2023
7db686a
Fix a line in the setup.bat script
Akul2010 Jul 21, 2023
968aec0
Fix setup scripts
Akul2010 Jul 27, 2023
c47b0ea
Change LICENSE from MIT to GNU GPLv3
Akul2010 Jul 29, 2023
64d3595
Update `README.md`
Akul2010 Jul 29, 2023
108f211
Removed tests folder because it was not needed
Akul2010 Jul 29, 2023
53843ec
Add all default plugins to core repository and remove the plugin down…
Akul2010 Jul 30, 2023
a6e6077
Start fixinng plugins and getting server running
Akul2010 Jul 30, 2023
985b10c
Update README.md
Akul2010 Aug 3, 2023
3237024
Update .gitignore
Akul2010 Aug 7, 2023
3b20912
Ignore all vosk model files for testing
Akul2010 Aug 7, 2023
6886d92
Merge branch 'master-v2' into add-plugin-api-v2
Akul2010 Aug 7, 2023
2dfb2d3
Shift API setup into the if condition at the end
Akul2010 Aug 14, 2023
b9385a3
Add plugin files
Akul2010 Aug 14, 2023
f5e1c12
Fix VOSK model not being able to read the model files
Akul2010 Aug 14, 2023
ed769fe
Update `.idea` folder for anaconda distribution
Akul2010 Aug 14, 2023
555c4b7
Remove packages we don't use
Akul2010 Aug 17, 2023
adf9b65
Stop the `Error loading ASGI app. Attribute "app" not found in module…
Akul2010 Aug 17, 2023
ea0cf96
Update akulai.py
Akul2010 Aug 18, 2023
983d19b
update js plugins
Akul2010 Oct 8, 2023
be4431e
Fixed all threading related errors, some other errors still remain
Akul2010 Nov 2, 2023
8ea5e5e
Strawberry Perl docs in short for new users
Akul2010 Nov 2, 2023
1dbce12
No more Pylance errors, but plugins don't work
Akul2010 Nov 10, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
# Files from setup
.gitmodules
akulai/akulai-plugins

# Ignore the VOSK model folder
akulai/model

# Ignore the plugins directory
akulai/plugins
# Ignore the VOSK model folder and everything in it
model/vosk_model

# IDE files and folders
./idea
Expand Down Expand Up @@ -177,3 +173,6 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

# JS Plugin-related
node_modules/
3 changes: 0 additions & 3 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +0,0 @@
[submodule "akulai-plugins"]
path = akulai-plugins
url = https://github.com/Akul-AI/akulai-plugins
6 changes: 4 additions & 2 deletions .idea/akulai.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 8 additions & 1 deletion .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"python.analysis.typeCheckingMode": "basic"
}
695 changes: 674 additions & 21 deletions LICENSE.md

Large diffs are not rendered by default.

10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,15 @@ If you want a more stable version (the current version is not done yet and may n

### Manual installation

First of all, download the ASR model from [here](https://alphacephei.com/vosk/models), unzip it, rename it to `vosk_model` and place it inside `models` folder, which is located in the `akulai` directory. Make sure that you have Node.js and any installation of Perl installed, and make sure cpamn (Perl's package manager) is in the system PATH for Windows.
First of all, download the ASR model from [here](https://alphacephei.com/vosk/models), unzip it, rename it to `vosk_model` and place it inside `models` folder, which is located in the `akulai` directory. Make sure that you have Node.js and any installation of Perl installed (Strawberry or System Perl recommended), and make sure `cpamn` (Perl's package manager) is in the system PATH for Windows.

### Automatic installation (with script)

Again, you may either follow the steps above, or you may use the provided python script. We have also provided a Batch (Windows CMD) and Bash (Shell) script. If you face any errors, however, please use the python script before reporting it.
Again, you may either follow the steps above, or you may use the provided python script. We have also provided a Batch (Windows CMD) and Bash (Shell) script. If you face any errors, please let us know.

Keep in mind that:
- All the scripts assume you have a working installation of Python installed.
- All the scripts assume you have a working installation of Python installed. They do not provide a Python installation like they do for Strawberry Perl and Node.js.
- If one of the native scripts don't work, try using the Python script.

## How do I create a skill...??

Expand All @@ -25,7 +26,7 @@ Go to the [master-v1 branch](https://github.com/Akul-AI/akulai/tree/master-v1) f

## Contribution

You can look at the `todo.md` file (not always updated) or you can look at the issues. Make sure to check the CONTRIBUTING.md guide before making a new feature/fix, though. Thanks to all the contributors!
You can look at the `todo.md` file (not always updated) or you can look at the issues. Make sure to check the `CONTRIBUTING.md` guide before making a new feature/fix, though. Thanks to all the contributors!

<a href="https://github.com/Akul-AI/akulai/graphs/contributors">
<img src="https://contrib.rocks/image?repo=Akul-AI/akulai" />
Expand All @@ -34,4 +35,5 @@ You can look at the `todo.md` file (not always updated) or you can look at the i

## Other Information
Version system - semantic versioning

Compatible with - Windows, Linux, Raspberry Pi
58 changes: 37 additions & 21 deletions akulai/akulai.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@
import importlib.util
import json
import os
import pyttsx3
import rlvoice
import pyaudio
import subprocess
import threading
import vosk
import sys
from fastapi import FastAPI
import time


class JSPlugin:
Expand All @@ -33,10 +36,15 @@ def __init__(self):
# Initialize the pyaudio device
self.p = pyaudio.PyAudio()
self.stream = self.p.open(format=pyaudio.paInt16, channels=1, rate=16000, input=True, frames_per_buffer=8000)
# Initialize the pyttsx3 speech engine
self.engine = pyttsx3.init()
# Initialize the rlvoice speech engine
self.engine = rlvoice.init()
self.voices = self.engine.getProperty('voices')
self.engine.setProperty('rate', 120)
self.engine.setProperty('voice', self.voices[0].id)
# Create the listening thread
self.stop_listening = threading.Event()
self.listening_thread = threading.Thread(target=self.listen)
self.listening_thread.start()
# load the plugins
self.discover_plugins()

Expand All @@ -55,12 +63,12 @@ def discover_plugins(self):
# Load the Python plugin using importlib
plugin_path = os.path.join(dirpath, filename)
spec = importlib.util.spec_from_file_location("plugin", plugin_path)
plugin = importlib.util.module_from_spec(spec)
spec.loader.exec_module(plugin)
plugin = importlib.util.module_from_spec(spec) # type: ignore
spec.loader.exec_module(plugin) # type: ignore

# Add the plugin to the list
self.plugins.append(plugin)
print(f"Loaded plugin from {plugin_path}")
print(f"Loaded python plugin: {plugin_path}")

elif filename.endswith(".pl"):
# Load the Perl plugin using subprocess
Expand All @@ -71,7 +79,7 @@ def discover_plugins(self):
# Add the plugin to the list
print(f"Loaded perl plugin: {plugin_path}")
except subprocess.CalledProcessError:
print(f"Error loading perl plugin: {plugin_path}")
print(f"Error loading perl plugin: {plugin_path}") # type: ignore

elif filename.endswith(".js"):
# Load the JavaScript plugin using subprocess
Expand All @@ -82,11 +90,11 @@ def discover_plugins(self):
# Add the plugin to the list
print(f"Loaded node plugin: {plugin_path}")
except subprocess.CalledProcessError:
print(f"Error loading node plugin: {plugin_path}")
print(f"Error loading node plugin: {plugin_path}") # type: ignore

# Listen for audio input through mic with pyaudio and vosk
def listen(self):
while not self.stop_listening.is_set():
while not self.stop_listening.is_set(): # type: ignore
data = self.stream.read(16000, exception_on_overflow=False)
if len(data) == 0:
break
Expand Down Expand Up @@ -116,22 +124,30 @@ def execute_command(self, command):
if(not handled):
self.speak("Sorry, I didn't understand that.")

def start(self):
self.speak("Hello, I am AkulAI. How can I help you today?")
# Create the listening thread
self.stop_listening = threading.Event()
self.listening_thread = threading.Thread(target=self.listen)
self.listening_thread.start()

# shut down the program and all threads
def stop(self):
self.stop_listening.set()
self.stop_listening.set() # type: ignore
self.stream.stop_stream()
self.stream.close()
self.p.terminate()
exit()
sys.exit()

# Set up API
app = FastAPI()
akulAI = AkulAI()

# Run API server
os.system("uvicorn akulai:app --reload")
time.sleep(5)

@app.post("/speak/{text}")
async def speak(text: str):
akulAI.speak(text)
return {"message": "Text synthesized"}

@app.post("/listen")
async def listen():
akulAI.listen()
return {"message": "Listening..."}

if __name__ == '__main__':
akulai = AkulAI()
akulai.start()
akulAI.speak("Hello, I am AkulAI. How can I help you today?")
51 changes: 41 additions & 10 deletions docs/create_plugins.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,33 +12,40 @@ Next, create a file in your sub-directory called `plugin.info`. It should look s
```
author: John Doe
dependencies: requests, pandas
description: Lorem ipsum di olor nulla quis lorem ut libero malesuada feugiat. This plugin.info file is an example. See the akulai_plugins repository for more examples.
description: Lorem ipsum di olor nulla quis lorem ut libero malesuada feugiat. This plugin.info file is an example.
```

The dependencies may vary based on your project. Note that when listing the dependencies, list them by the name you installed them. For example, if you installed a dependency with `pip install py-example`(note that this is an example, and applies to all languages), but imported it with `import example`, you would still list the dependency `as py-example`. If you have no dependencies required to be installed, just leave it blank.
The dependencies may vary based on your project. Note that when listing the dependencies, list them by the name you installed them. For example, if you installed a dependency with `pip install py-example`(note that this is an example, and applies to all languages), but imported it with `import example`, you would still list the dependency `as py-example`. If you have no dependencies required to be installed, just leave it blank. Note that default packages (such as `os` in python, which comes preinstalled with python) should nnot be included.

Moreover, all plugins now require calling an API which gives them access to the speak and listen function, allowing them to speak and listen when needed in the plugin. For using the speak function, it requires a GET request, and for listen, it requires a POST request. I have only included GET in this so far. If, for whatever reason, you do not require this, you may skip calling the API, and print in the console rather than using the `speak()` and `listen()` functions.

## Python Plugins
Python plugins should be written as a function called `handle()` with one parameter, `command`, which is the text of what the user said. It should return the text of AkulAI would like to say to the user.

Here is an example of a Python plugin that says "Hello, World!" when the user says "hello":

``` python
```python
import requests

response = requests.get('http://127.0.0.1:8000/speak')
def handle(command):
if "hello" in command:
return "Hello there!"
response.speak("Hello there!")
```
## Javascript Plugins
JavaScript plugins should read from the commandline and write to stdout using console.log().

Here is an example of a JavaScript plugin that says "Hello, World!" when the user says "hello":

``` javascript
```javascript
fetch('http://127.0.0.1:8000/speak')

command = ""
if(process.argv.length > 2){
command = process.argv[2]
}
if (command.indexOf("hello") !== -1){
console.log("Hello there!")
speak("Hello there!")
}
```
If you want to check for multiple words, you can use the `.indexOf()` method multiple times and use logical operators `(&&, ||, etc)` to check if multiple conditions are met.
Expand All @@ -54,29 +61,53 @@ if (command.includes("hello")){
```
or you can use `RegExp` to check if a string matches a specific pattern, like this:

``` javascript
```javascript
let match = command.match(/hello/);
if (match) {
console.log("Hello there!")
}
```
It all depends on your plugin and preference.

Keep in mind that when making plugins which require packages, the `node_modules` directory and the `package.json` file belong in the root directory.
Keep in mind that when making plugins which require packages, the `node_modules` directory and the `package.json` file belong in the directory of the plugin.

Here is an example of a `package.json` file for the inbuilt calendar skill:
```json
{
"name": "calendar",
"version": "1.0.0",
"description": "",
"main": "main.js",
"scripts": {
"test": "npm run dev",
"start": "node main.js"
},
"author": "Akul Goel",
"license": "ISC",
"dependencies": {
"fs": "^0.0.1-security"
}
}
```
<b>MAKE SURE YOU ONLY USE NPM, OTHER JS PACKAGE MANAGERS AREN'T CURRENTLY SUPPORTED.</b>

## Perl Plugins

Here is an example of a Perl plugin:

``` perl
#!/usr/bin/perl
use LWP::UserAgent;

my $ua = LWP::UserAgent->new;
my $response = $ua->get('http://127.0.0.1:8000/speak');

my $command = shift();
if($command=~/hello/){
print("Hello there!\n");
speak("Hello there!\n");
}
```
Perl plugins should read from the commandline and write to stdout using print(). This example uses the command variable to check if the command contains the word "hello" and if true, print a response for AkulAI to read.
This example uses the command variable to check if the command contains the word "hello" and if true, speak a response for AkulAI to read.

## Using a Plugin
Once a plugin has been created, it will automatically be loaded and available for use when the `AkulAI` class is instantiated. The `AkulAI` class will search for any files with the extensions of ".py" or ".js" in the "plugins" directory, and will add them to the list of available plugins.
Expand Down
50 changes: 50 additions & 0 deletions docs/strawberry-perl-docs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
=== Strawberry Perl (64-bit) 5.38.0.1-64bit README ===

What is Strawberry Perl?
------------------------

* 'Perl' is a programming language suitable for writing simple scripts as well
as complex applications. See https://perldoc.perl.org/perlintro.html

* 'Strawberry Perl' is a perl environment for Microsoft Windows containing all
you need to run and develop perl applications. It is designed to be as close
as possible to perl environment on UNIX systems. See https://strawberryperl.com/

* If you are completely new to perl consider visiting http2://learn.perl.org/

Installation instructions: (.ZIP distribution only, not .MSI installer)
-----------------------------------------------------------------------

* If installing this version from a .zip file, you MUST extract it to a
directory that does not have spaces in it - e.g. c:\myperl\
and then run some commands and manually set some environment variables:

c:\myperl\relocation.pl.bat ... this is REQUIRED!
c:\myperl\update_env.pl.bat ... this is OPTIONAL

You can specify " --nosystem" after update_env.pl.bat to install Strawberry
Perl's environment variables for the current user only.

* If having a fixed installation path does not suit you, try "Strawberry Perl
Portable Edition" from https://strawberryperl.com/releases.html

How to use Strawberry Perl?
---------------------------

* In the command prompt window you can:

1. run any perl script by launching

c:\> perl c:\path\to\script.pl

2. install additional perl modules (libraries) from https://www.metacpan.org/ by

c:\> cpanm Module::Name

3. run other tools included in Strawberry Perl like: perldoc, gcc, gmake ...

* You'll need a text editor to create perl scripts. One is NOT included with
Strawberry Perl. A few options are Padre (which can be installed by running
"cpan Padre" from the command prompt) and Notepad++ (which is downloadable at
https://notepad-plus-plus.org/ ) which both include syntax highlighting
for perl scripts. You can even use Notepad, if you wish.
14 changes: 0 additions & 14 deletions makefile

This file was deleted.

Loading