From e36837adf43fc153ab278c43fc116a5286f5cbdd Mon Sep 17 00:00:00 2001 From: Dani Date: Thu, 6 Apr 2023 20:59:16 -0400 Subject: [PATCH] First Stable Version of Eris --- .gitignore | 160 +++++++++++++++++++++++++++++++++++++++++ LICENSE | 21 ++++++ continuous_learning.py | 45 ++++++++++++ eris.db | Bin 0 -> 12288 bytes main.py | 82 +++++++++++++++++++++ rule_engine.py | 94 ++++++++++++++++++++++++ rules.py | 30 ++++++++ 7 files changed, 432 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 continuous_learning.py create mode 100644 eris.db create mode 100644 main.py create mode 100644 rule_engine.py create mode 100644 rules.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..68bc17f --- /dev/null +++ b/.gitignore @@ -0,0 +1,160 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# 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/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f7be8ec --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2023 [fullname] + +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. diff --git a/continuous_learning.py b/continuous_learning.py new file mode 100644 index 0000000..86f2d63 --- /dev/null +++ b/continuous_learning.py @@ -0,0 +1,45 @@ +from rule_engine import RuleEngine, Rule + +class ContinuousLearning: + def __init__(self, db_name): + # Initialize RuleEngine + self.rule_engine = RuleEngine(db_name) + + def add_initial_rules(self, rules): + # Add initial rules to the rule engine + for rule in rules: + rule_obj = Rule(rule[0], rule[1]) # Create Rule object from input tuple + self.rule_engine.add_rule(rule_obj) + + def handle_user_input(self, user_input): + # Match user input against rules + matched_rule = self.rule_engine.match_rule(user_input) + + if matched_rule: + # Retrieve output pattern from matched rule + response = matched_rule.output_pattern + else: + # No match found, generate default response + response = "I'm sorry, I didn't understand that." + + return response + + def capture_user_feedback(self, user_input, user_feedback): + # Update rule based on user feedback + # This is the continuous learning part, where the rule engine is updated with new data + # based on user feedback to improve its responses + matched_rule = self.rule_engine.match_rule(user_input) + if matched_rule: + matched_rule.output_pattern = user_feedback # Update output pattern of matched rule + self.rule_engine.update_rule(user_input, matched_rule.output_pattern) + + # Other methods for dynamic rule system functionality can be added here + + def process_message(self, user_input, user_feedback=None): + # Implement message processing logic here + # For example, you can call handle_user_input() to handle the user input + # and return the response generated from the matched rule's output pattern. + if user_feedback: + self.capture_user_feedback(user_input, user_feedback) + response = self.handle_user_input(user_input) + return response \ No newline at end of file diff --git a/eris.db b/eris.db new file mode 100644 index 0000000000000000000000000000000000000000..d7ad1b672bab87597e60e5cf0a7614e9eec8a181 GIT binary patch literal 12288 zcmeI$Pixdb6aesdcezK)ZT^BA&k)@ zN)bY3@kru1Us!yR&kthH&zG$-(qB9+lV7MrHWAq*UoK04|3Lr*KmY_l00ck)1V8`; zKmY_lVBQ2yDtJ*z8uRyvbK8pAh*QT zJvrMh47Z#-{GlvsnmY-6zLDnlCv@zO*xt0*XqZwj3DcQ;)9KiC-D$V1W@Af|*IhS= zC)7`h0(Uk>bRgRDA)T-&s!=Yw_&52@GnbszY}lfR_T7@aa>EV6ZIKaQ5ZBb(cHJ_K z8kW^tZjeW9hvH_Iu$kz3qf>9|&8FFEo6F*TD&!QAQ}U7gB;Uz5@_8O#G1LSCAOHd& z00JNY0w4eaAOHd&00RG^z@oH@#ppu0Dy`u^V~mCCl7?qz6f%+4@n3@nNs(4?5SErC J^-3`!_znBjuN(jX literal 0 HcmV?d00001 diff --git a/main.py b/main.py new file mode 100644 index 0000000..78d78d9 --- /dev/null +++ b/main.py @@ -0,0 +1,82 @@ +import asyncio +import os +import discord +from dotenv import load_dotenv +from continuous_learning import ContinuousLearning +from rule_engine import Rule, RuleEngine, RuleDB +from rules import all_rules # Import all_rules from rules.py + +load_dotenv() +TOKEN = os.getenv('DISCORD_TOKEN') +DB_NAME = os.getenv('DB_NAME') +intents = discord.Intents.default() +intents.members = True +intents.message_content = True +client = discord.Client(intents=intents) + +# Create an instance of RuleDB +rule_db = RuleDB(DB_NAME) + +# Create an instance of ContinuousLearning module +learning_engine = ContinuousLearning(rule_db) + +# Create an instance of RuleEngine +rule_engine = RuleEngine(rule_db) + +# Add rules from all_rules into the database +for rule in all_rules: + rule_db.add_rule(Rule(rule.input_pattern, rule.output_pattern)) + # Add rules from all_rules into the RuleEngine + rule_engine.add_rule(Rule(rule.input_pattern, rule.output_pattern)) + + +@client.event +async def on_ready(): + print(f'{client.user.name} has connected to Discord!') + +@client.event +async def on_message(message): + if message.author == client.user: + return + + # Process the message and generate a response using RuleEngine and ContinuousLearning + response = rule_engine.process_message(message.content) + if response is None: + response = learning_engine.process_message(message.content) + + # Send the response back to the same channel + response_message = await message.channel.send(response) + + # Add reactions for user feedback + for i in range(1, 6): + emoji_unicode = f'{i}\uFE0F\u20E3' + await response_message.add_reaction(emoji_unicode) + + # Define a check function for reaction event + def check(reaction, user): + return user == message.author and str(reaction.emoji) in [f'{i}\uFE0F\u20E3' for i in range(1, 6)] and reaction.message.id == response_message.id + + # Wait for user reaction + try: + reaction, _ = await client.wait_for('reaction_add', timeout=60.0, check=check) + feedback = int(reaction.emoji[0]) + # Pass the feedback to ContinuousLearning for updating rules + learning_engine.process_message(message.content, user_feedback=feedback) + except asyncio.TimeoutError: + feedback = None + + # Pass the feedback to the RuleEngine for updating rules + rule_engine.update_rule(message.content, feedback) + + # Check if the message is from Eris herself and not a user + if message.author == client.user: + # Check if the response is not None, indicating that Eris generated a response + if response is not None: + # Create a new rule with the input pattern as the original message content + # and the output pattern as the generated response + new_rule = Rule(input_pattern=message.content, output_pattern=response) + # Add the new rule to the RuleDB and RuleEngine + rule_db.add_rule(new_rule) + rule_engine.add_rule(new_rule) + +client.run(TOKEN) \ No newline at end of file diff --git a/rule_engine.py b/rule_engine.py new file mode 100644 index 0000000..7cab027 --- /dev/null +++ b/rule_engine.py @@ -0,0 +1,94 @@ +import sqlite3 +import datetime + +class Rule: + def __init__(self, input_pattern, output_pattern): + self.input_pattern = input_pattern + self.output_pattern = output_pattern + + def match(self, user_input): + # Implement matching logic here + # For example, you can check if the user_input matches the input_pattern + # using a regular expression or any other suitable matching technique + return self.input_pattern in user_input + + def update(self, user_input, user_feedback): + # Implement rule update logic here + pass + +class RuleDB: + def __init__(self, db_name): + self.conn = sqlite3.connect(db_name) + self.cursor = self.conn.cursor() + + # Create rules table if not exists + self.cursor.execute('''CREATE TABLE IF NOT EXISTS rules + (input_pattern TEXT PRIMARY KEY, output_pattern TEXT, last_changed TEXT)''') + self.conn.commit() + + def add_rule(self, rule): + # Add rule to the database, handle exceptions if rule already exists + try: + self.cursor.execute("INSERT INTO rules (input_pattern, output_pattern, last_changed) VALUES (?, ?, ?)", + (rule.input_pattern, rule.output_pattern, str(datetime.datetime.now()))) + self.conn.commit() + except sqlite3.IntegrityError: + print("Rule already exists in the database.") + + def update_rule(self, rule): + # Update the rule in the database + self.cursor.execute("UPDATE rules SET output_pattern = ?, last_changed = ? WHERE input_pattern = ?", + (rule.output_pattern, str(datetime.datetime.now()), rule.input_pattern)) + self.conn.commit() + + def get_rules(self): + # Retrieve rules from the database + self.cursor.execute("SELECT input_pattern, output_pattern FROM rules") + rows = self.cursor.fetchall() + rules = [] + for row in rows: + input_pattern, output_pattern = row + rule = Rule(input_pattern, output_pattern) + rules.append(rule) + return rules + +class RuleEngine: + def __init__(self, rule_db): + self.rules = [] + self.rule_db = rule_db + # Load rules from database + self.load_rules_from_db() + + def add_rule(self, rule): + # Add rule to the RuleDB + self.rule_db.add_rule(rule) + + def match_rule(self, user_input): + # Implement rule matching logic here + # Iterate through the rules and check if the user_input matches any of the input_patterns + # using the match() method of each rule + for rule in self.rules: + if rule.match(user_input): + return rule + return None + + def update_rule(self, user_input, user_feedback): + # Find the matching rule based on user input + matching_rule = self.match_rule(user_input) + if matching_rule: + # Update the matching rule with user feedback + matching_rule.update(user_input, user_feedback) + # Update the rule in the RuleDB + self.rule_db.update_rule(matching_rule) + + def load_rules_from_db(self): + # Load rules from the RuleDB and populate the rules list + self.rules = self.rule_db.get_rules() + + def process_message(self, user_input): + # Implement message processing logic here + # For example, you can call match_rule() to find the matching rule, + # and return the corresponding output pattern from the matched rule. + matching_rule = self.match_rule(user_input) + if matching_rule: + return matching_rule.output_pattern \ No newline at end of file diff --git a/rules.py b/rules.py new file mode 100644 index 0000000..8838d1a --- /dev/null +++ b/rules.py @@ -0,0 +1,30 @@ +# rules.py + +class Rules: + def __init__(self, input_pattern, output_pattern): + self.input_pattern = input_pattern + self.output_pattern = output_pattern + +# Original rules +original_rules = [ + Rules("hello", "Hi there!"), + Rules("hi", "Hello!"), + Rules("how are you", "I'm doing well, thank you!"), + Rules("what's your name", "My name is Eris!"), + Rules("goodbye", "Goodbye!"), + Rules("bye", "See you later!"), + # Add more original rules as needed +] + +# User feedback-based rules +user_feedback_rules = [ + # Add rules based on user feedback here +] + +# Autonomous rules +autonomous_rules = [ + # Add rules developed by the chatbot on its own free-will here +] + +# Combine all sets of rules into a single list +all_rules = original_rules + user_feedback_rules + autonomous_rules \ No newline at end of file