Skip to content

Commit

Permalink
MARVIN-00: Notion automation
Browse files Browse the repository at this point in the history
  • Loading branch information
AJV009 committed Sep 5, 2023
1 parent 75f7f4f commit 5b6540f
Show file tree
Hide file tree
Showing 18 changed files with 332 additions and 125 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ OPENAI_API_KEY=sk-<your-openai-api-key>
5. Once you feel you are ready, just run `pm2 start ecosystem.config.js`

## Note
- You can tailor Marvins system prompt at `marvin/data/prompts.json`
- You can tailor Marvins system prompt at `marvinslackbot/data/prompts.json`
- Whenever you mention @ marvin, it will reply. (It should be added to the channel)
- What's with the deal of double messages in thread? So my slack bot is currently extremely slow, its not only my network problem but the whole socket based connection thing and multiple unnecessary loops. So I have added some (>99,<101) dummy responses in a file in the project dir itself, it replies with :thinking_face::brain::zap: in a thread to give you an assurance that the event has been triggered, acknowledged and you have to wait for the response. (But these hard coded responses from the bot is ignored when passing all the thread data to chat api)
- By default it uses the 'gpt-3.5-turbo' model, but with a flag you can use GPT-4 as well. But nah I won't tell you how to, just figure it out. Read the code or checkout the sample below...
Expand Down
2 changes: 1 addition & 1 deletion main.sh
Original file line number Diff line number Diff line change
@@ -1 +1 @@
~/Envs/marvin/bin/python -m marvin.main
~/Envs/marvinslackbot/bin/python -m marvinslackbot.main
70 changes: 0 additions & 70 deletions marvin/main.py

This file was deleted.

File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
219 changes: 219 additions & 0 deletions marvinslackbot/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
import os
from datetime import datetime, timedelta
import time
import copy
import requests
from slack_bolt import App
from dotenv import load_dotenv
from marvinslackbot.utils.slack.connect import SlackConnection
from marvinslackbot.utils.slack.helpers import SlackHelpers
from marvinslackbot.utils.openai.endpoint import OpenAIHelpers

# Load environment variables
load_dotenv()

# Initialize the Slack app
app = App(token=os.environ.get("SLACK_BOT_TOKEN"))

# Event handler for app mentions
@app.event("app_mention")
def handle_mention(body, say):
# Start the process
if ('thread_ts' in body['event']):
# Acknowledge the event
say(openai.thinking(), thread_ts=body["event"]["ts"])

# Fetch thread messages
messages = slack_helpers.fetch_thread_messages(
body['event']['channel'], body['event']['thread_ts']
)
else:
# Create a message list from the event data
messages = [{
"user_id": body["event"]["user"],
"user": slack_helpers.userid_to_realname(body["event"]["user"]),
"message": slack_helpers.replace_usernames(body["event"]["text"])
}]

# Process messages and respond if needed
if messages:
data = openai.set_data(messages)
if data["moderation"]:
say(data["moderation"], thread_ts=body["event"]["ts"])
else:
say(openai.chat(), thread_ts=body["event"]["ts"])

# Command to delete Marvin posted messages
@app.command("/marvin-delete")
def handle_delete_command(ack, respond, command):
url = command['text']
ack()
if not url:
respond('Please provide a message URL', response_type='ephemeral')
return
result = slack_helpers.extract_timestamp_and_channel(url)
if not result:
respond('Invalid message URL', response_type='ephemeral')
return
channel_id, timestamp = result
message = slack_helpers.delete_message(channel_id, timestamp)
respond(message, response_type='ephemeral')

@app.event("message")
def handle_message_events(body, logger):
logger.info(body)

# [TEMPORARY] Notion automation for a specific user
@app.command("/week-log")
def summarize_week(ack, respond, command):
ack()

trigger_happy = slack_helpers.post_message(command["channel_id"], "/week-log triggered. \nFetching data from Notion 'AI Integration Workspace' page",)

try:
today = datetime.now().date()
days_until_last_monday = today.weekday() + 7 if today.weekday() <= 3 else today.weekday()
last_monday = today - timedelta(days=days_until_last_monday)
days_until_friday = 4 - today.weekday() if today.weekday() <= 3 else (4 + 7) - today.weekday()
last_friday = last_monday + timedelta(days=days_until_friday)
if last_friday > today:
last_monday -= timedelta(days=7)
last_friday -= timedelta(days=7)

db_query = f"https://api.notion.com/v1/databases/{os.environ.get('NOTION_DB_ID')}/query"
page_query = "https://api.notion.com/v1/pages/"
block_query = "https://api.notion.com/v1/blocks/"

headers = {
"Authorization": "Bearer " + os.environ.get('NOTION_INTEGRATION_SECRET'),
"Notion-Version": "2022-06-28",
"Content-Type": "application/json"
}

data = {
"filter": {
"property": "Date",
"date": {
"on_or_after": str(last_monday),
"on_or_before": str(last_friday + timedelta(days=2)) # taking Sunday into account
}
}
}

complete_notion_context = ""
res = requests.post(db_query, json=data, headers=headers)
tasks = res.json()['results']
tasks.reverse()
t_id = 1
for task in tasks:
current_page_query = page_query+task['id']+"/properties/title"
res = requests.get(current_page_query, headers=headers)
page = res.json()['results'][0]
page_title = page['title']['plain_text']
complete_notion_context += ("Note " + str(t_id) + "\n Title: " + page_title + "\n")

SKIP_BLOCK = False
if not SKIP_BLOCK:
current_page_block_children = block_query+task['id']+"/children"
res = requests.get(current_page_block_children, headers=headers)
blocks = res.json()['results']
block_text = ""
for block in blocks:
if "paragraph" in block:
rich_texts = block['paragraph']['rich_text']
for rich_text in rich_texts:
block_text += rich_text['plain_text'] + "\n"
complete_notion_context += ("Content: " + block_text + "\n\n")

_ = slack_helpers.message_update(command["channel_id"], trigger_happy['ts'], "/week-log triggered. \nFetching data from Notion 'AI Integration Workspace' page. \nProcessed Note " + str(t_id) + ": " + page_title + " (Created on " + task['created_time'] + ")")

t_id += 1

_ = slack_helpers.message_update(command["channel_id"], trigger_happy['ts'], "Data fetched from Notion 'AI Integration Workspace'. \nSummarizing the data using GPT-4.")

openai_system_message = {"role": "system", "content": """
You are my very useful personal assistant.
Following are some things to keep in mind:
1. You have been placed inside a Notion workspace to help me create note or tasks summary. Every week I report to my investors about my progress.
2. When I give you a list of task notes, you understand it and summarize it for me. Don't skip any note in the summary.
3. Keep it less dreamy and more realistic.
4. Add additional information if you think it is necessary.
5. Since the investors see this report it needs to be professional, well formatted, easy to understand and also filled with details in a way that pleases them and more potential investors.
6. Use markdown formatting. Keep it in bullet points.
7. Never ask the user or me anything, since we can't respond to you. Its a one way request.
8. Never mention of the investors, because we don't want to be looking like pleading to them.
9. Keep it short, concise and to the point. Don't inlcude any unnecessary information.
10. Your response is directly shared with the investors without moderation, so no need to say stuff like `here is a summary` or `this is a summary` or 'we are doing this and that'.
Remember your only task is to simple return a summarised report in bullets
"""}

openai_user_message = {"role": "user", "content": f"""
Following are the details extracted from the 'AI Integration Workspace' notion database.
```
{complete_notion_context}
```
Summarize the above information, make bullet points, and add additional information if you think it is necessary.
"""}

openai_message_array = [openai_system_message, openai_user_message]
SKIP_GENERATION = False
if not SKIP_GENERATION:
init_openai = openai.return_openai()
openai_response = init_openai.ChatCompletion.create(
model='gpt-3.5-turbo',
messages=openai_message_array,
temperature=0,
stream=True # this time, we set stream=True
)

complete_openai_response = ""
update_interval = 2 # Set the update interval to 2 seconds
last_update_time = time.time()

slack_response_template = [
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": f"Weekly log fetched from Notion 'AI Integration Workspace' page: ({last_monday} to {last_friday}) \n[Summarised using GPT-4]\n\n{{openai_response}}"
}
}
]

for chunk in openai_response:
if 'content' in chunk['choices'][0]['delta']:
complete_openai_response += chunk['choices'][0]['delta']['content']

current_time = time.time()
if current_time - last_update_time >= update_interval:
slack_response_block = copy.deepcopy(slack_response_template)
slack_response_block[0]["text"]["text"] = slack_response_block[0]["text"]["text"].replace("{openai_response}", complete_openai_response)
_ = slack_helpers.message_update(command["channel_id"], trigger_happy['ts'], blocks=slack_response_block, text="streaming response")
last_update_time = current_time

# After processing all chunks, make one final update if needed
if complete_openai_response:
slack_response_block = copy.deepcopy(slack_response_template)
slack_response_block[0]["text"]["text"] = slack_response_block[0]["text"]["text"].replace("{openai_response}", complete_openai_response)
_ = slack_helpers.message_update(command["channel_id"], trigger_happy['ts'], blocks=slack_response_block, text="streaming response")
except Exception as e:
print(e)
_ = slack_helpers.message_update(command["channel_id"], trigger_happy['ts'], "Error occured: " + str(e) + "\n\nMessage will be auto deleted in 20 seconds.")
time.sleep(20)
_ = slack_helpers.delete_message(command["channel_id"], trigger_happy['ts'])


# Main entry point
if __name__ == "__main__":
print("Script is running and monitoring for changes...")
# Initialize SlackHelpers
slack_helpers = SlackHelpers()
slack_helpers.set_app(app)

# Initialize OpenAIHelpers
openai = OpenAIHelpers()
openai.thinking_setup()

# Initialize SlackConnection (should be last)
slack_connect = SlackConnection(app)
slack_connect.socket()
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from marvin.utils.autogpt.web_helper import WebHelper
from marvinslackbot.utils.autogpt.web_helper import WebHelper

class AutoGPT:

Expand Down
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,21 @@
import os
from os.path import exists
import random
from marvin.utils.slack.helpers import SlackHelpers
from marvin.utils.autogpt.autogpt import AutoGPT
from marvinslackbot.utils.slack.helpers import SlackHelpers
from marvinslackbot.utils.autogpt.autogpt import AutoGPT


class OpenAIHelpers:
def __init__(self):
openai.organization = os.environ.get("OPENAI_ORG_ID")
openai.api_key = os.environ.get("OPENAI_API_KEY")
self.thiking_file = "marvin/data/thinking.txt"
self.thiking_file = "marvinslackbot/data/thinking.txt"
self.thinking_thoughts = []
self.slack_helpers = SlackHelpers(init_self_app=True)
self.messages = []
with open("marvin/data/prompts.json", "r") as f:
with open("marvinslackbot/data/prompts.json", "r") as f:
self.prompts = json.load(f)
with open("marvin/data/flags.json", "r") as f:
with open("marvinslackbot/data/flags.json", "r") as f:
self.flags = json.load(f)

# Prefetch thinking thoughts
Expand Down Expand Up @@ -94,6 +94,16 @@ def model_extractor(self):

return model

def quickChat(self, messages, model):
response = openai.ChatCompletion.create(
model=model,
messages=messages,
)
return response['choices'][0]['message']['content']

def return_openai(self):
return openai

def chat(self):
# empty response
response = None
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,14 @@ def extract_timestamp_and_channel(self, url):

return None

def get_channel_name(self, channel_id):
try:
channel_info = self.app.client.conversations_info(channel=channel_id)
channel_name = channel_info["channel"]["name"]
return channel_name
except SlackApiError as e:
return channel_id

def delete_message(self, channel_id, ts):
try:
response = self.app.client.chat_delete(channel=channel_id, ts=ts)
Expand All @@ -91,10 +99,8 @@ def delete_message(self, channel_id, ts):
message = f'Error deleting message: {e}'
return message

def get_channel_name(self, channel_id):
try:
channel_info = self.app.client.conversations_info(channel=channel_id)
channel_name = channel_info["channel"]["name"]
return channel_name
except SlackApiError as e:
return channel_id
def post_message(self, channel, text=None, blocks=None):
return self.app.client.chat_postMessage(channel=channel, text=text, blocks=blocks)

def message_update(self, channel, ts, text=None, blocks=None):
return self.app.client.chat_update(channel=channel, ts=ts, text=text, blocks=blocks)
File renamed without changes.
Loading

0 comments on commit 5b6540f

Please sign in to comment.