From 5b6540fe642cd325ac6abbd9d54f8614635285ec Mon Sep 17 00:00:00 2001 From: Alphons Jaimon Date: Tue, 5 Sep 2023 05:34:47 +0530 Subject: [PATCH] MARVIN-00: Notion automation --- README.md | 2 +- main.sh | 2 +- marvin/main.py | 70 ------ {marvin => marvinslackbot}/__init__.py | 0 {marvin => marvinslackbot}/data/flags.json | 0 {marvin => marvinslackbot}/data/prompts.json | 0 {marvin => marvinslackbot}/data/thinking.txt | 0 marvinslackbot/main.py | 219 ++++++++++++++++++ .../utils/autogpt/autogpt.py | 2 +- .../utils/autogpt/ocr_helper.py | 0 .../utils/autogpt/web_helper.py | 0 .../utils/openai/endpoint.py | 20 +- .../utils/slack/connect.py | 0 .../utils/slack/helpers.py | 20 +- .../utils/utils/utils.py | 0 requirements.txt | 102 ++++---- setup.py | 2 +- test.json | 18 ++ 18 files changed, 332 insertions(+), 125 deletions(-) delete mode 100644 marvin/main.py rename {marvin => marvinslackbot}/__init__.py (100%) rename {marvin => marvinslackbot}/data/flags.json (100%) rename {marvin => marvinslackbot}/data/prompts.json (100%) rename {marvin => marvinslackbot}/data/thinking.txt (100%) create mode 100644 marvinslackbot/main.py rename {marvin => marvinslackbot}/utils/autogpt/autogpt.py (92%) rename {marvin => marvinslackbot}/utils/autogpt/ocr_helper.py (100%) rename {marvin => marvinslackbot}/utils/autogpt/web_helper.py (100%) rename {marvin => marvinslackbot}/utils/openai/endpoint.py (87%) rename {marvin => marvinslackbot}/utils/slack/connect.py (100%) rename {marvin => marvinslackbot}/utils/slack/helpers.py (91%) rename {marvin => marvinslackbot}/utils/utils/utils.py (100%) create mode 100644 test.json diff --git a/README.md b/README.md index d3c6f03..0513e4e 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ OPENAI_API_KEY=sk- 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... diff --git a/main.sh b/main.sh index c1364ea..33a5ee0 100644 --- a/main.sh +++ b/main.sh @@ -1 +1 @@ -~/Envs/marvin/bin/python -m marvin.main +~/Envs/marvinslackbot/bin/python -m marvinslackbot.main diff --git a/marvin/main.py b/marvin/main.py deleted file mode 100644 index 69ad5c4..0000000 --- a/marvin/main.py +++ /dev/null @@ -1,70 +0,0 @@ -import os -from slack_bolt import App -from dotenv import load_dotenv -from marvin.utils.slack.connect import SlackConnection -from marvin.utils.slack.helpers import SlackHelpers -from marvin.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') - -# Main entry point -if __name__ == "__main__": - # 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() diff --git a/marvin/__init__.py b/marvinslackbot/__init__.py similarity index 100% rename from marvin/__init__.py rename to marvinslackbot/__init__.py diff --git a/marvin/data/flags.json b/marvinslackbot/data/flags.json similarity index 100% rename from marvin/data/flags.json rename to marvinslackbot/data/flags.json diff --git a/marvin/data/prompts.json b/marvinslackbot/data/prompts.json similarity index 100% rename from marvin/data/prompts.json rename to marvinslackbot/data/prompts.json diff --git a/marvin/data/thinking.txt b/marvinslackbot/data/thinking.txt similarity index 100% rename from marvin/data/thinking.txt rename to marvinslackbot/data/thinking.txt diff --git a/marvinslackbot/main.py b/marvinslackbot/main.py new file mode 100644 index 0000000..b26482f --- /dev/null +++ b/marvinslackbot/main.py @@ -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() diff --git a/marvin/utils/autogpt/autogpt.py b/marvinslackbot/utils/autogpt/autogpt.py similarity index 92% rename from marvin/utils/autogpt/autogpt.py rename to marvinslackbot/utils/autogpt/autogpt.py index 1f9f1c8..63a0d70 100644 --- a/marvin/utils/autogpt/autogpt.py +++ b/marvinslackbot/utils/autogpt/autogpt.py @@ -1,4 +1,4 @@ -from marvin.utils.autogpt.web_helper import WebHelper +from marvinslackbot.utils.autogpt.web_helper import WebHelper class AutoGPT: diff --git a/marvin/utils/autogpt/ocr_helper.py b/marvinslackbot/utils/autogpt/ocr_helper.py similarity index 100% rename from marvin/utils/autogpt/ocr_helper.py rename to marvinslackbot/utils/autogpt/ocr_helper.py diff --git a/marvin/utils/autogpt/web_helper.py b/marvinslackbot/utils/autogpt/web_helper.py similarity index 100% rename from marvin/utils/autogpt/web_helper.py rename to marvinslackbot/utils/autogpt/web_helper.py diff --git a/marvin/utils/openai/endpoint.py b/marvinslackbot/utils/openai/endpoint.py similarity index 87% rename from marvin/utils/openai/endpoint.py rename to marvinslackbot/utils/openai/endpoint.py index 185434e..c4da436 100644 --- a/marvin/utils/openai/endpoint.py +++ b/marvinslackbot/utils/openai/endpoint.py @@ -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 @@ -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 diff --git a/marvin/utils/slack/connect.py b/marvinslackbot/utils/slack/connect.py similarity index 100% rename from marvin/utils/slack/connect.py rename to marvinslackbot/utils/slack/connect.py diff --git a/marvin/utils/slack/helpers.py b/marvinslackbot/utils/slack/helpers.py similarity index 91% rename from marvin/utils/slack/helpers.py rename to marvinslackbot/utils/slack/helpers.py index a4fb0ed..c3ff284 100644 --- a/marvin/utils/slack/helpers.py +++ b/marvinslackbot/utils/slack/helpers.py @@ -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) @@ -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 \ No newline at end of file + 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) diff --git a/marvin/utils/utils/utils.py b/marvinslackbot/utils/utils/utils.py similarity index 100% rename from marvin/utils/utils/utils.py rename to marvinslackbot/utils/utils/utils.py diff --git a/requirements.txt b/requirements.txt index ceaedcb..5417104 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,56 +1,80 @@ -aiohttp==3.8.4 +aiohttp==3.8.5 aiosignal==1.3.1 -async-timeout==4.0.2 +annotated-types==0.5.0 +anyio==3.7.1 +async-timeout==4.0.3 attrs==23.1.0 -autopep8==2.0.2 +autopep8==2.0.4 beautifulsoup4==4.12.2 -cachetools==5.3.0 -certifi==2022.12.7 -charset-normalizer==3.1.0 -dataclasses-json==0.5.7 -frozenlist==1.3.3 -google-api-core==2.11.0 -google-auth==2.17.3 -google-cloud-core==2.3.2 -google-cloud-storage==2.8.0 -google-cloud-vision==3.4.1 +cachetools==5.3.1 +certifi==2023.7.22 +charset-normalizer==3.2.0 +click==8.1.7 +dataclasses-json==0.5.14 +fastapi==0.103.1 +frozenlist==1.4.0 +google-api-core==2.11.1 +google-auth==2.22.0 +google-cloud-core==2.3.3 +google-cloud-storage==2.10.0 +google-cloud-vision==3.4.4 google-crc32c==1.5.0 -google-resumable-media==2.4.1 -googleapis-common-protos==1.59.0 -gptcache==0.1.12 +google-resumable-media==2.5.0 +googleapis-common-protos==1.60.0 +gptcache==0.1.40 greenlet==2.0.2 -grpcio==1.54.0 -grpcio-status==1.54.0 +grpcio==1.57.0 +grpcio-status==1.57.0 +h11==0.14.0 +httpcore==0.17.3 +httpx==0.24.1 idna==3.4 -langchain==0.0.141 -marshmallow==3.19.0 +Jinja2==3.1.2 +jsonpatch==1.33 +jsonpointer==2.4 +langchain==0.0.279 +langsmith==0.0.33 +markdown-it-py==3.0.0 +MarkupSafe==2.1.3 +marshmallow==3.20.1 marshmallow-enum==1.5.1 -e . +mdurl==0.1.2 multidict==6.0.4 mypy-extensions==1.0.0 -numpy==1.24.2 -openai==0.27.4 -openapi-schema-pydantic==1.2.4 +numexpr==2.8.5 +numpy==1.25.2 +openai==0.28.0 packaging==23.1 -proto-plus==1.22.2 -protobuf==4.22.3 +proto-plus==1.22.3 +protobuf==4.24.2 pyasn1==0.5.0 pyasn1-modules==0.3.0 -pycodestyle==2.10.0 -pydantic==1.10.7 +pycodestyle==2.11.0 +pydantic==2.3.0 +pydantic_core==2.6.3 +Pygments==2.16.1 python-dotenv==1.0.0 -PyYAML==6.0 -requests==2.28.2 +PyYAML==6.0.1 +regex==2023.8.8 +requests==2.31.0 +rich==13.5.2 rsa==4.9 six==1.16.0 -slack-bolt==1.17.1 -slack-sdk==3.21.1 -soupsieve==2.4.1 -SQLAlchemy==1.4.47 -tenacity==8.2.2 +slack-bolt==1.18.0 +slack-sdk==3.21.3 +sniffio==1.3.0 +soupsieve==2.5 +SQLAlchemy==2.0.20 +starlette==0.27.0 +tenacity==8.2.3 +tiktoken==0.4.0 tomli==2.0.1 -tqdm==4.65.0 -typing-inspect==0.8.0 -typing_extensions==4.5.0 -urllib3==1.26.15 -yarl==1.8.2 +tqdm==4.66.1 +typer==0.9.0 +typing-inspect==0.9.0 +typing_extensions==4.7.1 +tzdata==2023.3 +urllib3==1.26.16 +uvicorn==0.23.2 +yarl==1.9.2 diff --git a/setup.py b/setup.py index 1c0d251..5046fd7 100644 --- a/setup.py +++ b/setup.py @@ -1,7 +1,7 @@ from setuptools import setup, find_packages setup( - name="marvin", + name="marvinslackbot", version="0.1", packages=find_packages(), ) diff --git a/test.json b/test.json new file mode 100644 index 0000000..8f31006 --- /dev/null +++ b/test.json @@ -0,0 +1,18 @@ +{ + 'object': 'list', + 'results': [ + {'object': 'page', 'id': 'b70bc55c-cee4-4721-b9b8-4db6ccbd0758', 'created_time': '2023-09-04T00: 14: 00.000Z', 'last_edited_time': '2023-09-04T00: 16: 00.000Z', 'created_by': {'object': 'user', 'id': '56ade062-1589-4c42-91b9-f8c2a3271c03' + }, 'last_edited_by': {'object': 'user', 'id': '56ade062-1589-4c42-91b9-f8c2a3271c03' + }, 'cover': None, 'icon': None, 'parent': {'type': 'database_id', 'database_id': 'c70a3b36-a79f-4ea8-92e6-ae829e316790' + }, 'archived': False, 'properties': {'Tags': {'id': 'R%5EoF', 'type': 'multi_select', 'multi_select': [] + }, 'Date': {'id': '_Pl%60', 'type': 'date', 'date': {'start': '2023-09-01', 'end': None, 'time_zone': None + } + }, 'Person': {'id': '%7BGb%7D', 'type': 'people', 'people': [] + }, 'Name': {'id': 'title', 'type': 'title', 'title': [ + {'type': 'text', 'text': {'content': 'Explored something about fine-tuned Embedding', 'link': None + }, 'annotations': {'bold': False, 'italic': False, 'strikethrough': False, 'underline': False, 'code': False, 'color': 'default' + }, 'plain_text': 'Explored something about fine-tuned Embedding', 'href': None + } + ] + } + }, 'url': 'https: //www.notion.so/Explored-something-about-fine-tuned-Embedding-b70bc55ccee44721b9b84db6ccbd0758', 'public_url': None}, {'object': 'page', 'id': '11a88577-e96e-433c-8f98-7af34f2c36a7', 'created_time': '2023-09-04T00:14:00.000Z', 'last_edited_time': '2023-09-04T00:14:00.000Z', 'created_by': {'object': 'user', 'id': '56ade062-1589-4c42-91b9-f8c2a3271c03'}, 'last_edited_by': {'object': 'user', 'id': '56ade062-1589-4c42-91b9-f8c2a3271c03'}, 'cover': None, 'icon': None, 'parent': {'type': 'database_id', 'database_id': 'c70a3b36-a79f-4ea8-92e6-ae829e316790'}, 'archived': False, 'properties': {'Tags': {'id': 'R%5EoF', 'type': 'multi_select', 'multi_select': []}, 'Date': {'id': '_Pl%60', 'type': 'date', 'date': {'start': '2023-09-01', 'end': None, 'time_zone': None}}, 'Person': {'id': '%7BGb%7D', 'type': 'people', 'people': [{'object': 'user', 'id': '56ade062-1589-4c42-91b9-f8c2a3271c03'}]}, 'Name': {'id': 'title', 'type': 'title', 'title': [{'type': 'text', 'text': {'content': 'Filtering down on potential AI Solutions', 'link': None}, 'annotations': {'bold': False, 'italic': False, 'strikethrough': False, 'underline': False, 'code': False, 'color': 'default'}, 'plain_text': 'Filtering down on potential AI Solutions', 'href': None}]}}, 'url': 'https://www.notion.so/Filtering-down-on-potential-AI-Solutions-11a88577e96e433c8f987af34f2c36a7', 'public_url': None}, {'object': 'page', 'id': '598e5827-ec88-4da9-835d-ddefa197d288', 'created_time': '2023-09-04T00:14:00.000Z', 'last_edited_time': '2023-09-04T00:24:00.000Z', 'created_by': {'object': 'user', 'id': '56ade062-1589-4c42-91b9-f8c2a3271c03'}, 'last_edited_by': {'object': 'user', 'id': '56ade062-1589-4c42-91b9-f8c2a3271c03'}, 'cover': None, 'icon': None, 'parent': {'type': 'database_id', 'database_id': 'c70a3b36-a79f-4ea8-92e6-ae829e316790'}, 'archived': False, 'properties': {'Tags': {'id': 'R%5EoF', 'type': 'multi_select', 'multi_select': []}, 'Date': {'id': '_Pl%60', 'type': 'date', 'date': {'start': '2023-09-01', 'end': None, 'time_zone': None}}, 'Person': {'id': '%7BGb%7D', 'type': 'people', 'people': [{'object': 'user', 'id': '56ade062-1589-4c42-91b9-f8c2a3271c03'}]}, 'Name': {'id': 'title', 'type': 'title', 'title': [{'type': 'text', 'text': {'content': 'working out a PoC on ‘user journey tracking’', 'link': None}, 'annotations': {'bold': False, 'italic': False, 'strikethrough': False, 'underline': False, 'code': False, 'color': 'default'}, 'plain_text': 'working out a PoC on ‘user journey tracking’', 'href': None}]}}, 'url': 'https://www.notion.so/working-out-a-PoC-on-user-journey-tracking-598e5827ec884da9835dddefa197d288', 'public_url': None}, {'object': 'page', 'id': 'bb52f477-51ef-472d-80a6-4bdc434fb18d', 'created_time': '2023-08-31T06:31:00.000Z', 'last_edited_time': '2023-08-31T06:31:00.000Z', 'created_by': {'object': 'user', 'id': '56ade062-1589-4c42-91b9-f8c2a3271c03'}, 'last_edited_by': {'object': 'user', 'id': '56ade062-1589-4c42-91b9-f8c2a3271c03'}, 'cover': None, 'icon': None, 'parent': {'type': 'database_id', 'database_id': 'c70a3b36-a79f-4ea8-92e6-ae829e316790'}, 'archived': False, 'properties': {'Tags': {'id': 'R%5EoF', 'type': 'multi_select', 'multi_select': []}, 'Date': {'id': '_Pl%60', 'type': 'date', 'date': {'start': '2023-08-31', 'end': None, 'time_zone': None}}, 'Person': {'id': '%7BGb%7D', 'type': 'people', 'people': [{'object': 'user', 'id': '56ade062-1589-4c42-91b9-f8c2a3271c03'}]}, 'Name': {'id': 'title', 'type': 'title', 'title': [{'type': 'text', 'text': {'content': 'working out a PoC on ‘user journey tracking’', 'link': None}, 'annotations': {'bold': False, 'italic':False, 'strikethrough': False, 'underline': False, 'code': False, 'color': 'default'}, 'plain_text': 'working out a PoC on ‘user journey tracking’', 'href': None}]}}, 'url': 'https://www.notion.so/working-out-a-PoC-on-user-journey-tracking-bb52f47751ef472d80a64bdc434fb18d', 'public_url': None}, {'object': 'page', 'id': '4a90448d-dbf7-4e11-afcb-f84e6ef6ebf1', 'created_time': '2023-08-31T06:30:00.000Z', 'last_edited_time': '2023-09-04T00:20:00.000Z', 'created_by': {'object': 'user', 'id': '56ade062-1589-4c42-91b9-f8c2a3271c03'}, 'last_edited_by': {'object': 'user', 'id': '56ade062-1589-4c42-91b9-f8c2a3271c03'}, 'cover': None, 'icon': None, 'parent': {'type': 'database_id', 'database_id': 'c70a3b36-a79f-4ea8-92e6-ae829e316790'}, 'archived': False, 'properties': {'Tags': {'id': 'R%5EoF', 'type': 'multi_select', 'multi_select': []}, 'Date': {'id': '_Pl%60', 'type': 'date', 'date': {'start': '2023-08-31', 'end': None, 'time_zone': None}}, 'Person': {'id': '%7BGb%7D', 'type': 'people', 'people': [{'object': 'user', 'id': '56ade062-1589-4c42-91b9-f8c2a3271c03'}]}, 'Name': {'id': 'title', 'type': 'title', 'title': [{'type': 'text', 'text': {'content': 'Filtering down on potential AI Solutions', 'link': None}, 'annotations': {'bold': False, 'italic': False, 'strikethrough': False, 'underline': False, 'code': False, 'color': 'default'}, 'plain_text': 'Filtering down on potential AI Solutions', 'href': None}]}}, 'url': 'https://www.notion.so/Filtering-down-on-potential-AI-Solutions-4a90448ddbf74e11afcbf84e6ef6ebf1', 'public_url': None}, {'object': 'page', 'id': '69a6f249-3df9-4ad4-a2f5-b45ad24f9dac', 'created_time': '2023-08-30T02:37:00.000Z', 'last_edited_time': '2023-09-04T00:21:00.000Z', 'created_by': {'object': 'user', 'id': '56ade062-1589-4c42-91b9-f8c2a3271c03'}, 'last_edited_by': {'object': 'user', 'id': '56ade062-1589-4c42-91b9-f8c2a3271c03'}, 'cover': None, 'icon': None, 'parent': {'type': 'database_id', 'database_id': 'c70a3b36-a79f-4ea8-92e6-ae829e316790'}, 'archived': False, 'properties': {'Tags': {'id': 'R%5EoF', 'type': 'multi_select', 'multi_select': []}, 'Date': {'id': '_Pl%60', 'type': 'date', 'date': {'start': '2023-08-30', 'end': None, 'time_zone': None}}, 'Person': {'id': '%7BGb%7D', 'type': 'people', 'people': [{'object': 'user', 'id': '56ade062-1589-4c42-91b9-f8c2a3271c03'}]}, 'Name': {'id': 'title', 'type': 'title', 'title': [{'type': 'text', 'text': {'content': 'working out a PoC on ‘user journey tracking’', 'link': None}, 'annotations': {'bold': False, 'italic': False, 'strikethrough': False, 'underline': False, 'code': False, 'color': 'default'}, 'plain_text': 'working out a PoC on ‘user journey tracking’', 'href': None}]}}, 'url': 'https://www.notion.so/working-out-a-PoC-on-user-journey-tracking-69a6f2493df94ad4a2f5b45ad24f9dac', 'public_url': None}, {'object': 'page', 'id': 'a9d6fc23-7618-4739-856c-593e39a80c6f', 'created_time': '2023-08-30T02:35:00.000Z', 'last_edited_time': '2023-08-31T06:30:00.000Z', 'created_by': {'object': 'user', 'id': '56ade062-1589-4c42-91b9-f8c2a3271c03'}, 'last_edited_by': {'object': 'user', 'id': '56ade062-1589-4c42-91b9-f8c2a3271c03'}, 'cover': None, 'icon': None, 'parent': {'type': 'database_id', 'database_id': 'c70a3b36-a79f-4ea8-92e6-ae829e316790'}, 'archived': False, 'properties': {'Tags': {'id': 'R%5EoF', 'type': 'multi_select', 'multi_select': []}, 'Date': {'id': '_Pl%60', 'type': 'date', 'date': {'start': '2023-08-30', 'end': None, 'time_zone': None}}, 'Person': {'id': '%7BGb%7D', 'type': 'people', 'people': [{'object': 'user', 'id': '56ade062-1589-4c42-91b9-f8c2a3271c03'}]}, 'Name': {'id': 'title', 'type': 'title', 'title': [{'type': 'text', 'text': {'content': 'Filtering down on potential AI Solutions', 'link': None}, 'annotations': {'bold': False, 'italic': False, 'strikethrough': False, 'underline': False, 'code': False, 'color': 'default'}, 'plain_text': 'Filtering down on potential AI Solutions', 'href': None}]}}, 'url': 'https://www.notion.so/Filtering-down-on-potential-AI-Solutions-a9d6fc2376184739856c593e39a80c6f', 'public_url': None}, {'object': 'page', 'id': 'bcacea45-e580-4342-bf32-a4f4e5228a05', 'created_time': '2023-08-30T02:32:00.000Z', 'last_edited_time':'2023-08-30T02:36:00.000Z', 'created_by': {'object': 'user', 'id': '56ade062-1589-4c42-91b9-f8c2a3271c03'}, 'last_edited_by': {'object': 'user', 'id': '56ade062-1589-4c42-91b9-f8c2a3271c03'}, 'cover': None, 'icon': None, 'parent': {'type': 'database_id', 'database_id': 'c70a3b36-a79f-4ea8-92e6-ae829e316790'}, 'archived': False, 'properties': {'Tags': {'id': 'R%5EoF', 'type': 'multi_select', 'multi_select': []}, 'Date': {'id': '_Pl%60', 'type': 'date', 'date': {'start': '2023-08-29', 'end': None, 'time_zone': None}}, 'Person': {'id': '%7BGb%7D', 'type': 'people', 'people': [{'object': 'user', 'id': '56ade062-1589-4c42-91b9-f8c2a3271c03'}]}, 'Name': {'id': 'title', 'type': 'title', 'title': [{'type': 'text', 'text': {'content': 'Filtering down on potential AI solutions', 'link': None}, 'annotations': {'bold': False, 'italic': False, 'strikethrough': False, 'underline': False, 'code': False, 'color': 'default'}, 'plain_text': 'Filtering down on potential AI solutions', 'href': None}]}}, 'url': 'https://www.notion.so/Filtering-down-on-potential-AI-solutions-bcacea45e5804342bf32a4f4e5228a05', 'public_url': None}, {'object': 'page', 'id': '8be6a8a1-ec9f-4bbf-817e-08474c3ebd0d', 'created_time': '2023-08-30T02:30:00.000Z', 'last_edited_time': '2023-08-30T02:36:00.000Z', 'created_by': {'object': 'user', 'id': '56ade062-1589-4c42-91b9-f8c2a3271c03'}, 'last_edited_by': {'object': 'user','id': '56ade062-1589-4c42-91b9-f8c2a3271c03'}, 'cover': None, 'icon': None, 'parent': {'type': 'database_id', 'database_id': 'c70a3b36-a79f-4ea8-92e6-ae829e316790'}, 'archived': False, 'properties': {'Tags': {'id': 'R%5EoF', 'type': 'multi_select', 'multi_select': []}, 'Date': {'id': '_Pl%60', 'type': 'date', 'date': {'start': '2023-08-29', 'end':None, 'time_zone': None}}, 'Person': {'id': '%7BGb%7D', 'type': 'people', 'people': [{'object': 'user', 'id': '56ade062-1589-4c42-91b9-f8c2a3271c03'}]}, 'Name': {'id': 'title', 'type': 'title', 'title': [{'type': 'text', 'text': {'content': 'Exploring Unomi', 'link': None}, 'annotations': {'bold': False, 'italic': False, 'strikethrough': False, 'underline': False, 'code': False, 'color': 'default'}, 'plain_text': 'Exploring Unomi', 'href': None}]}}, 'url': 'https://www.notion.so/Exploring-Unomi-8be6a8a1ec9f4bbf817e08474c3ebd0d', 'public_url': None}, {'object': 'page', 'id': '4678ae4e-4e91-44c5-ac67-b2dba16bc1db', 'created_time': '2023-08-28T18:06:00.000Z', 'last_edited_time': '2023-08-30T02:37:00.000Z', 'created_by': {'object': 'user', 'id': '56ade062-1589-4c42-91b9-f8c2a3271c03'}, 'last_edited_by': {'object': 'user', 'id': '56ade062-1589-4c42-91b9-f8c2a3271c03'}, 'cover': None, 'icon': None, 'parent': {'type': 'database_id', 'database_id': 'c70a3b36-a79f-4ea8-92e6-ae829e316790'}, 'archived': False, 'properties': {'Tags': {'id': 'R%5EoF', 'type': 'multi_select', 'multi_select': []}, 'Date': {'id': '_Pl%60', 'type': 'date', 'date': {'start': '2023-08-28', 'end': None, 'time_zone': None}}, 'Person': {'id': '%7BGb%7D', 'type': 'people', 'people': [{'object': 'user', 'id': '56ade062-1589-4c42-91b9-f8c2a3271c03'}]}, 'Name': {'id': 'title', 'type': 'title', 'title': [{'type': 'text', 'text': {'content': 'Setup a sample Unomi and Mautic demo site', 'link': None}, 'annotations': {'bold': False, 'italic': False, 'strikethrough': False, 'underline': False, 'code': False, 'color': 'default'}, 'plain_text': 'Setup a sample Unomi and Mautic demo site', 'href': None}]}}, 'url': 'https://www.notion.so/Setup-a-sample-Unomi-and-Mautic-demo-site-4678ae4e4e9144c5ac67b2dba16bc1db', 'public_url': None}], 'next_cursor': None, 'has_more': False, 'type': 'page_or_database', 'page_or_database': {}} \ No newline at end of file