From 7d431b0b510c47f2d4990c56550e7062aaf045aa Mon Sep 17 00:00:00 2001 From: samerbam Date: Thu, 22 Jun 2023 13:14:13 -0400 Subject: [PATCH] updates --- README.md | 32 ++++++++++++ backend/__init__.py | 0 backend/api.py | 0 backend/auth.py | 9 +++- backend/main.py | 19 +++++-- backend/requirements.txt | 4 ++ backend/skills/auth.py | 1 + backend/skills/timers.py | 103 ++++++++++++++++++++++++++++++-------- backend/skills/utility.py | 10 ++++ 9 files changed, 154 insertions(+), 24 deletions(-) create mode 100644 README.md create mode 100644 backend/__init__.py create mode 100644 backend/api.py create mode 100644 backend/requirements.txt create mode 100644 backend/skills/auth.py create mode 100644 backend/skills/utility.py diff --git a/README.md b/README.md new file mode 100644 index 0000000..57599ef --- /dev/null +++ b/README.md @@ -0,0 +1,32 @@ +# A Siri like AI Assistant +--- + +* Uses ChatGPT for general queries +* Uses Wolfram Alpha for anything math related +* Has built in NLP (using a NLI model) for determining if we can process query locally +* Frontend/Backend architecture for ability to deploy lightweight clients + + +## Skills +--- + +[ ] Alarms +[ ] Calendar +[ ] Gmail +[ ] ChatGPT +[ ] Reminders +[ ] Timers +[ ] Todos +[ ] Weather +[ ] Wolfram +[x] NLP +[x] Speech to Text (frontend for sure) +[ ] API + [ ] Authentication + [ ] General API +[ ] TTS + - generate audio on backend or frontend? + - Perks of backend is fast generation + - Cons of backend is large file transfers between devices, lots of internet usage + - Perks of frontend is less data transfer between devices requiring less internet usage + - Cons of frontend is slower generation diff --git a/backend/__init__.py b/backend/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/api.py b/backend/api.py new file mode 100644 index 0000000..e69de29 diff --git a/backend/auth.py b/backend/auth.py index b2a43db..b205ffb 100644 --- a/backend/auth.py +++ b/backend/auth.py @@ -1 +1,8 @@ -# TODO: Handle all authentication stuff for verifying client is who we think it is in here. \ No newline at end of file +# TODO: Handle all authentication stuff for verifying client is who we think it is in here. + +# Example: https://github.com/miguelgrinberg/REST-auth + +# User/Pass for initial token and refresh token generation, this should +# OAuth2 style authentication +# Flask-RESTFul with +# \ No newline at end of file diff --git a/backend/main.py b/backend/main.py index f101906..0d06cd5 100644 --- a/backend/main.py +++ b/backend/main.py @@ -7,11 +7,24 @@ from skills.timers import Timers from skills.todos import Todos from skills.weather import Weather from skills.wolfram import Wolfram +from NLP import NLP + +import sys +print(sys.version) skills = [GPT(), Alarms(), Cal(), Gmail(), Reminders(), Timers(), Todos(), Weather(), Wolfram()] +skill_names = [skill.trigger_phrase for skill in skills] +print("test") if __name__ == "__main__": - print("Skill Trigger Phrases: ") - for skill in skills: - print(skill.trigger_phrase) \ No newline at end of file + # print("Skill Trigger Phrases: ") + + print(f"Active Skills: {skill_names}") + + + nlp = NLP() + + + # for skill in skills: + # print(skill.trigger_phrase) \ No newline at end of file diff --git a/backend/requirements.txt b/backend/requirements.txt new file mode 100644 index 0000000..6a98b79 --- /dev/null +++ b/backend/requirements.txt @@ -0,0 +1,4 @@ +transformers +spacy +schedule +ctparse \ No newline at end of file diff --git a/backend/skills/auth.py b/backend/skills/auth.py new file mode 100644 index 0000000..b2a43db --- /dev/null +++ b/backend/skills/auth.py @@ -0,0 +1 @@ +# TODO: Handle all authentication stuff for verifying client is who we think it is in here. \ No newline at end of file diff --git a/backend/skills/timers.py b/backend/skills/timers.py index e399b89..d156f6a 100644 --- a/backend/skills/timers.py +++ b/backend/skills/timers.py @@ -1,44 +1,107 @@ -from skills.config import ntfy_url import requests +if __name__ == "__main__": # Handle + from config import ntfy_url +else: + from skills.config import ntfy_url + + +import threading +import schedule +import time + +# def job_that_executes_once(): + # Do some work that only needs to happen once... + # return schedule.CancelJob + + +def run_continuously(schedule, interval=1): + # Borrowed from schedule documentation, why reinvent the wheel when its been created. + """Continuously run, while executing pending jobs at each + elapsed time interval. + @return cease_continuous_run: threading. Event which can + be set to cease continuous run. Please note that it is + *intended behavior that run_continuously() does not run + missed jobs*. For example, if you've registered a job that + should run every minute and you set a continuous run + interval of one hour then your job won't be run 60 times + at each interval but only once. + """ + cease_continuous_run = threading.Event() + + class ScheduleThread(threading.Thread): + @classmethod + def run(cls): + while not cease_continuous_run.is_set(): + schedule.run_pending() + time.sleep(interval) + + continuous_thread = ScheduleThread() + continuous_thread.start() + return cease_continuous_run + + +# while True: +# schedule.run_pending() +# time.sleep(1) + + class Timers: def __init__(self): self.trigger_phrase = "timer" self.timers = {} + self.schedule = schedule.Scheduler() - def _add_timer(self, time, name): - self.timers[name] = time - # use https://schedule.readthedocs.io/en/stable/examples.html#run-a-job-once to trigger self._trigger_timer() - def _remove_timer(self, name): - del self.timers[name] - - def _trigger_timer(self, name): - if name in self.timers: - r = requests.post(f"https://ntfy.sh/{ntfy_url}", - data=f"{name}", + def _notify(self, device_id, timer_name): + r = requests.post(f"https://ntfy.sh/{device_id}", + data=f"{timer_name}", headers={ "Title": "Your timer is going off!", "Priority": "default", "Tags": "bell" }) - print(r.text) - #TODO: send ntfy.sh to device - #TODO: play timer done sound + return r + + def _add_timer(self, time, name): + if len(self.timers) == 0: + self.stop_run_continuously = run_continuously() + self.timers[name] = time + # duration = + self.schedule.every().day.at(time.strftime("%H:%M:%S", time.gmtime(duration))).do(self._trigger_timer(name)).tag(name) + # use https://schedule.readthedocs.io/en/stable/examples.html#run-a-job-once to trigger self._trigger_timer() + + def _remove_timer(self, name): + del self.timers[name] + if len(self.timers) == 0: + self.stop_run_continuously.set() + + def _trigger_timer(self, name): + if name in self.timers: + res = self._notify(ntfy_url, name).text + print(res) + + self._remove_timer(name) + + # #TODO: play timer done sound + return schedule.CancelJob + + def get_remaining_time(self, name=""): + pass + # if name == "": - def run(self, query): + def run(self, query="", time=0, name=""): if "add" in query: - time = 0 #TODO: Natural Language parse time out of phrase - self._add_timer(time) + self._add_timer(time, name) return True # Return true to indicate success if "remove" in query: - time = 0 #TODO: Natural Language parse time out of phrase - self._remove_timer(time) + self._remove_timer(name) return True return False # Return false to indicate failure - + def _disable_timer_check_thread(self): + self.stop_run_continuously.set() if __name__ == "__main__": diff --git a/backend/skills/utility.py b/backend/skills/utility.py new file mode 100644 index 0000000..3ef8faf --- /dev/null +++ b/backend/skills/utility.py @@ -0,0 +1,10 @@ +from ctparse import ctparse +from datetime import datetime + +def parsetime(phrase): + ts = datetime.now() + return ctparse(phrase, ts=ts) + + +if __name__ == "__main__": + print(parsetime('May 5th 2:30 in the afternoon').ref_time) \ No newline at end of file