├── .env.example
├── .gitignore
├── LICENSE
├── README.md
├── backend
├── .env.example
├── agent.py
├── composio_config.py
├── firebase
│ ├── __pycache__
│ │ └── init.cpython-312.pyc
│ └── init.py
├── initialize_sheet_agent.py
├── main.py
├── requirements.txt
└── setup.sh
├── eslint.config.js
├── index.html
├── package-lock.json
├── package.json
├── postcss.config.js
├── public
└── vite.svg
├── src
├── App.css
├── App.jsx
├── assets
│ ├── brain.svg
│ └── react.svg
├── components
│ ├── ActionButton.jsx
│ ├── AddAgent.jsx
│ ├── Avatar.jsx
│ ├── BenefitCard.jsx
│ ├── Benefits.jsx
│ ├── FAQ.jsx
│ ├── Footer.jsx
│ ├── Hero.jsx
│ ├── LogoComponent.jsx
│ ├── Navbar.jsx
│ ├── ResponsiveMessage.jsx
│ ├── ScrollToTop.jsx
│ ├── SettingsAttribute.jsx
│ ├── SkeletonLoader.jsx
│ ├── SmallButton.jsx
│ ├── Working.jsx
│ └── WorkingFlow.jsx
├── config
│ └── firebase.js
├── index.css
├── main.jsx
├── pages
│ ├── Agent.jsx
│ ├── Dashboard.jsx
│ ├── Home.jsx
│ ├── Login.jsx
│ ├── NotFound.jsx
│ └── Settings.jsx
└── utils
│ └── authUtils.js
├── tailwind.config.js
├── vercel.json
└── vite.config.js
/.env.example:
--------------------------------------------------------------------------------
1 | VITE_FIREBASE_API_KEY=
2 | VITE_FIREBASE_AUTH_DOMAIN=
3 | VITE_FIREBASE_PROJECT_ID=
4 | VITE_FIREBASE_STORAGE_BUCKET=
5 | VITE_FIREBASE_MESSAGING_SENDER_ID=
6 | VITE_FIREBASE_APP_ID=
7 | VITE_FIREBASE_MEASUREMENT_ID=
8 | VITE_BACKEND_URL=
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 | yarn-debug.log*
6 | yarn-error.log*
7 | pnpm-debug.log*
8 | lerna-debug.log*
9 |
10 | .env
11 | backend/firebase/genius-57d8d-firebase-adminsdk-ue7u9-90656332c6.json
12 | backend/attachments
13 |
14 | backend/__pycache__
15 | backend/genius-57d8d-firebase-adminsdk-ue7u9-90656332c6.json
16 |
17 | node_modules
18 | dist
19 | dist-ssr
20 | *.local
21 |
22 | # Editor directories and files
23 | .vscode/*
24 | !.vscode/extensions.json
25 | .idea
26 | .DS_Store
27 | *.suo
28 | *.ntvs*
29 | *.njsproj
30 | *.sln
31 | *.sw?
32 |
33 | backend/.env
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Abhishek Patil
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # ⚡️GmailGenius: Supercharge your Gmail
2 | *Automatically processes new emails, extracts data from attachments, and organizes everything in a spreadsheet!*
3 |
4 | ## Why GmailGenius?
5 |
6 | I developed GmailGenius because managing invoices felt like trying to find a needle in a haystack—while blindfolded! I was constantly losing track of due dates and getting hit with late fees, which turned my financial life into a chaotic mess. So, I thought, why not create an AI buddy to handle the email chaos? With GmailGenius, users can connect their Gmail, specify keywords, and let the AI scan for invoices, extracting all the important details straight into Google Sheets. I built the AI agents using CrewAI, which allowed me to create specialized agents tailored for this task. The real magic comes from [Composio (https://app.composio.dev)’s powerful [Gmail](https://app.composio.dev/app/gmail) and [Google sheets](https://app.composio.dev/app/googlesheets) tools, which made integrating these features a breeze—just plug and play! Thanks to Composio, I could focus on creating a seamless user experience while my AI genius does the heavy lifting. Now you can say goodbye to late fees and hello to a more organized inbox!
7 |
8 | #### Checkout how others are using GmailGenius:
9 | [](https://dev.to/composiodev/i-built-an-ai-tool-to-handle-my-moms-invoices-and-saved-her-20-hours-of-work-44h1)
10 | [](https://www.reddit.com/r/selfhosted/comments/1f7f8f4/i_built_an_ai_tool_to_handle_my_moms_invoices_and/)
11 |
12 | ## Demo
13 | ### Check it out on Replit
14 | [](https://replit.com/@abishkpatil/gmail-assistant-fb)
15 |
16 | ### Live Demo ([Live Link](https://gmail-assistant-six.vercel.app/))
17 | [](https://drive.google.com/file/d/1_CWZ3yNK4pxe8Ey1bnQq4C6H_lEHDICb/preview)
18 |
19 | ## Description
20 | GmailGenius simplifies the process of finding relevant emails, downloading attachments, and extracting key data. Here's how it works:
21 |
22 | 1. **Sign up on GmailGenius** and link your Gmail account and Google Sheet
23 | 2. **Enter keywords** you want the AI agent to look for in your email
24 | 3. **GmailGenius finds emails and attachments** from Gmail that match your keyword criteria
25 | 4. **Useful information from the attachments is extracted and stored** in your linked Google Sheet.
26 |
27 | ### Under the hood, the AI agent divides the task into multiple steps and executes them:
28 |
29 |
30 |
31 | 1. **Retrieves emails from Gmail** that match the keyword/phrase criteria.
32 | 2. **Downloads** the relevant attachments.
33 | 3. **Extracts useful attributes** from the email body & attachments.
34 | 4. **Stores** the extracted data in the linked Google Sheet.
35 |
36 | ## Tech Stack
37 | - Frontend: ReactJS, Vite, TailwindCSS
38 | - Backend: Python, FastAPI
39 | - AI Agent: CrewAI, Composio, OpenAI
40 | - Composio tools: [Gmail](https://app.composio.dev/app/gmail), [Google Sheets](https://app.composio.dev/app/googlesheets)
41 |
42 | ## Run Locally
43 | ### Setup tutorial
44 | [](https://drive.google.com/file/d/1kC9oVSUatqQ6Tcs3u6CTsVsmczzG-F6k/preview)
45 |
46 | Clone the project
47 |
48 | ```bash
49 | git clone https://github.com/ComposioHQ/cookbook.git
50 | ```
51 |
52 | Go to the project directory
53 |
54 | ```bash
55 | cd gmail-assistant/gmail-assistant-firebase
56 | ```
57 |
58 | ### Backend
59 |
60 | Go to backend dir & run setup script, this will create a virtual environment & download necessary libraries (Note: if you're unable to execute then grant permisson -> chmod +x setup.sh)
61 | You'll then be prompted to login to **Composio**, link **Gmail** & **Google Sheets**.
62 | Add API keys in **.env file**
63 |
64 | ```bash
65 | cd backend && ./setup.sh
66 | ```
67 |
68 | Start the server
69 |
70 | ```bash
71 | python main.py
72 | ```
73 |
74 | Start the agent
75 |
76 | ```bash
77 | python agent.py
78 | ```
79 |
80 | ### Frontend
81 |
82 | Install dependencies
83 |
84 | ```bash
85 | npm install
86 | ```
87 |
88 | Start the server
89 |
90 | ```bash
91 | npm run dev
92 | ```
93 |
94 | ### Composio Login
95 | If you're prompted to login & enter API key, run the below command to login
96 |
97 | ```bash
98 | composio login
99 | ```
100 |
101 | You'll be redirected to composio website, login, get the API key and paste it
102 |
103 |
--------------------------------------------------------------------------------
/backend/.env.example:
--------------------------------------------------------------------------------
1 | OPENAI_API_KEY=
2 | MODEL=
3 | NANO_API_KEY=
4 | NANO_URL=
5 | FRONTEND_URL=
6 |
--------------------------------------------------------------------------------
/backend/agent.py:
--------------------------------------------------------------------------------
1 | import os
2 | import re
3 | import glob
4 | import json
5 | from composio.client.collections import TriggerEventData
6 | from composio_crewai import Action, ComposioToolSet
7 | from crewai import Agent, Crew, Task, Process
8 | from crewai_tools.tools.base_tool import BaseTool
9 | from langchain_openai import ChatOpenAI
10 | from dotenv import load_dotenv
11 | from typing import Any, Dict
12 | import requests
13 | from firebase.init import update_row
14 | from firebase.init import db
15 | # get_user_by_username
16 | from pathlib import Path
17 |
18 | load_dotenv()
19 |
20 | llm = ChatOpenAI(model="gpt-4o")
21 |
22 |
23 | class incrementCounter(BaseTool):
24 | name: str = "Increment Counter"
25 | description: str = "This tool increments the counter value for a user"
26 |
27 | def _run(self, uid: str, current_row: str) -> bool:
28 | new_row = int(current_row) + 1
29 | success = update_row(uid, str(new_row))
30 | if success:
31 | return f"Counter incremented. New row value: {new_row}"
32 | else:
33 | return "Failed to increment counter"
34 |
35 |
36 | # # Get the attachment that was recently downloaded
37 | # def get_recent_attachment() -> str:
38 | # pdf_files = glob.glob(os.path.join("/Users/abhishekpatil/.composio/output/", "*.pdf")) # modify path as per need
39 | # if not pdf_files:
40 | # return None
41 |
42 | # most_recent_pdf = max(pdf_files, key=os.path.getctime)
43 | # return most_recent_pdf
44 |
45 |
46 | # Extract useful attributes from attachment
47 | class extractorTool(BaseTool):
48 | name: str = "ExtractorTool"
49 | description: str = "This tool extracts useful attributes from pdf document/attachments"
50 |
51 | def _run(self, file_path: str) -> Dict[str, Any]:
52 | url = os.environ.get("NANO_URL")
53 | FilePath = {'file': open(file_path, 'rb')}
54 | response = requests.post(url,
55 | auth=requests.auth.HTTPBasicAuth(
56 | os.environ.get("NANO_API_KEY"), ''),
57 | files=FilePath)
58 | return json.loads(response.text)["result"][0]["prediction"]
59 |
60 |
61 | # Trigger instance
62 | composio_toolset1 = ComposioToolSet(api_key=os.environ.get("COMPOSIO_API_KEY"))
63 | listener = composio_toolset1.create_trigger_listener()
64 |
65 |
66 | @listener.callback(filters={"trigger_name": "GMAIL_NEW_GMAIL_MESSAGE"})
67 | def callback_new_message(event: TriggerEventData) -> None:
68 | print("Received new email")
69 | payload = event.payload
70 |
71 | def get_user_by_username(username):
72 | users_ref = db.collection('users')
73 | query = users_ref.where('username', '==', username).limit(1)
74 | docs = query.get()
75 |
76 | for doc in docs:
77 | return doc.to_dict()
78 |
79 | return False
80 |
81 | user = get_user_by_username(event.metadata.connection.clientUniqueUserId)
82 | keywords = user['sheetsConfig']['keywords']
83 | attributes = user['sheetsConfig']['attributes']
84 | sheetId = user['sheetsConfig']['spreadsheet_id']
85 | sheetName = "Sheet1"
86 | row = user['sheetsConfig']['row']
87 | uid = user['uid']
88 | # Tools
89 | composio_toolset = ComposioToolSet(
90 | api_key=os.environ.get("COMPOSIO_API_KEY"),
91 | output_dir=Path.cwd() / "attachments",
92 | entity_id=event.metadata.connection.clientUniqueUserId)
93 | tools = composio_toolset.get_actions(actions=[
94 | Action.GMAIL_GET_ATTACHMENT, Action.GOOGLESHEETS_BATCH_UPDATE
95 | ]) + [extractorTool(), incrementCounter()]
96 |
97 | # Agent
98 | google_assistant = Agent(
99 | role="Google Assistant",
100 | goal=
101 | "Process emails, check for keywords, download attachments, extract attributes, and store in Google Sheets",
102 | backstory=
103 | "You're an AI assistant that processes emails, extracts information from attachments, and stores data in Google Sheets.",
104 | verbose=True,
105 | llm=llm,
106 | tools=tools,
107 | allow_delegation=False,
108 | )
109 |
110 | process_new_email = Task(
111 | description=f"""
112 | 1. Check if the email subject (subject) or body (messageText) in the payload contains any of these keywords: {keywords}, Payload: {payload}.
113 | 2. If keywords match, download the attachment using the GMAIL_GET_ATTACHMENT action.
114 | 3. Use the Extractor_tool, pass the file path of the downloaded attachment to extract content from it
115 | 4. From the extracted content, identify the following attributes: {attributes}.
116 | 5. Store the identified attributes in Google Sheet with ID {sheetId}, sheet name {sheetName}, in cell A{row}. Just add the attribute value and not the attribute name. Do not use includeValuesInResponse attribute.
117 | 6. Increment the row counter, if and only if you updated the Google Sheet otherwise do not increment the row counter. Use the IncrementCounter tool, pass the uid value: {uid} and current row value: {row} as arguments.
118 | """,
119 | agent=google_assistant,
120 | expected_output=
121 | "Summary of email processing, including whether keywords matched, attachment was processed, and data was stored in the Google Sheet.",
122 | )
123 |
124 | email_processing_crew = Crew(
125 | agents=[google_assistant],
126 | tasks=[process_new_email],
127 | verbose=1,
128 | process=Process.sequential,
129 | )
130 | result = email_processing_crew.kickoff()
131 | return result
132 |
133 |
134 | print("Email trigger listener activated!")
135 | listener.listen()
136 |
--------------------------------------------------------------------------------
/backend/composio_config.py:
--------------------------------------------------------------------------------
1 | from composio import ComposioToolSet, App, Composio, Action
2 | from composio.client.exceptions import NoItemsFound
3 | from firebase.init import db
4 | import os
5 | from dotenv import load_dotenv
6 | load_dotenv()
7 |
8 | def update_gmail_trigger_status(ent_id: str) -> bool:
9 | users_ref = db.collection('users')
10 | query = users_ref.where('username', '==', ent_id).limit(1)
11 | docs = query.get()
12 |
13 | for doc in docs:
14 | try:
15 | doc.reference.update({'gmailTriggerEnabled': True})
16 | return True
17 | except Exception as e:
18 | print(f"Error updating trigger status: {e}")
19 | return False
20 |
21 | print(f"User with entity id {ent_id} not found")
22 | return False
23 |
24 |
25 | def enable_gmail_trigger(ent_id: str):
26 | client = Composio()
27 | try:
28 | entity = client.get_entity(id=ent_id)
29 | trigger_config = {'userId': 'me', 'interval': 1, 'labelIds': 'INBOX'}
30 | trigger_name = 'GMAIL_NEW_GMAIL_MESSAGE'
31 |
32 | entity.enable_trigger(app=App.GMAIL,
33 | trigger_name=trigger_name,
34 | config=trigger_config)
35 |
36 | response = {
37 | "status": "success",
38 | "message": f"Trigger {trigger_name} enabled for {ent_id} on Gmail"
39 | }
40 |
41 | if update_gmail_trigger_status(ent_id):
42 | return response
43 | else:
44 | return {
45 | "status":
46 | "partial_success",
47 | "message":
48 | f"Trigger enabled, but failed to update user status for {ent_id}"
49 | }
50 |
51 | except Exception as e:
52 | return {
53 | "status": "error",
54 | "message": f"Failed to enable trigger: {str(e)}"
55 | }
56 |
57 |
58 | def isEntityConnected(ent_id: str, appType: str):
59 | toolset = ComposioToolSet(api_key=os.environ.get("COMPOSIO_API_KEY"),
60 | entity_id=ent_id)
61 | entity = toolset.get_entity()
62 | app_enum = getattr(App, appType)
63 | try:
64 | entity.get_connection(app=app_enum)
65 | response = {
66 | "authenticated": "yes",
67 | "message": f"User {ent_id} is authenticated with {appType}",
68 | }
69 | return response
70 | except NoItemsFound as e:
71 | response = {
72 | "authenticated": "no",
73 | "message": f"User {ent_id} is not authenticated with {appType}",
74 | }
75 | return response
76 |
77 |
78 | def createNewEntity(ent_id: str, appType: str, redirectUrl: str):
79 | toolset = ComposioToolSet(api_key=os.environ.get("COMPOSIO_API_KEY"),
80 | entity_id=ent_id)
81 | entity = toolset.get_entity()
82 | app_enum = getattr(App, appType)
83 | try:
84 | entity.get_connection(app=app_enum)
85 | response = {
86 | "authenticated": "yes",
87 | "message":
88 | f"User {ent_id} is already authenticated with {appType}",
89 | "url": ""
90 | }
91 | return response
92 |
93 | except NoItemsFound as e:
94 | # Create a request to initiate connection
95 | request = entity.initiate_connection(
96 | app_enum,
97 | redirect_url=redirectUrl)
98 | response = {
99 | "authenticated": "no",
100 | "message":
101 | f"User {ent_id} is not yet authenticated with {appType}. Please authenticate.",
102 | "url": request.redirectUrl
103 | }
104 | return response
105 | # Poll until the connection is active
106 | connected_account = request.wait_until_active(client=toolset.client,
107 | timeout=100)
108 |
109 |
--------------------------------------------------------------------------------
/backend/firebase/__pycache__/init.cpython-312.pyc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/abhishekpatil4/GmailGenius/208363fec5e7d75da21a3a35114adf0a68299d45/backend/firebase/__pycache__/init.cpython-312.pyc
--------------------------------------------------------------------------------
/backend/firebase/init.py:
--------------------------------------------------------------------------------
1 | import firebase_admin
2 | from firebase_admin import credentials, auth, firestore
3 | from pathlib import Path
4 | import os
5 | from dotenv import load_dotenv
6 |
7 | load_dotenv()
8 |
9 | cred = credentials.Certificate(f"{Path.cwd()}/firebase/genius-57d8d-firebase-adminsdk-ue7u9-90656332c6.json")
10 | creds = {
11 | "type": os.environ.get("type"),
12 | "project_id": os.environ.get("project_id"),
13 | "private_key_id": os.environ.get("private_key_id"),
14 | "private_key": os.environ.get("private_key"),
15 | "client_email": os.environ.get("client_email"),
16 | "client_id": os.environ.get("client_id"),
17 | "auth_uri": os.environ.get("auth_uri"),
18 | "token_uri": os.environ.get("token_uri"),
19 | "auth_provider_x509_cert_url":
20 | os.environ.get("auth_provider_x509_cert_url"),
21 | "client_x509_cert_url": os.environ.get("client_x509_cert_url"),
22 | }
23 | # cred = credentials.Certificate(creds)
24 | firebase_admin.initialize_app(cred)
25 |
26 | db = firestore.client()
27 |
28 | # def get_user_by_username(username):
29 | # users_ref = db.collection('users')
30 | # query = users_ref.where('username', '==', username).limit(1)
31 | # docs = query.get()
32 |
33 | # for doc in docs:
34 | # return doc.to_dict()
35 |
36 | # return False
37 |
38 |
39 | def get_user_by_username(username):
40 | users_ref = db.collection('users')
41 | query = users_ref.where('uid', '==', username).limit(1)
42 | docs = query.get()
43 |
44 | for doc in docs:
45 | return doc.to_dict()
46 |
47 | return False
48 |
49 |
50 | def update_row(uid, new_row):
51 | users_ref = db.collection('users')
52 | query = users_ref.where('uid', '==', uid).limit(1)
53 | docs = query.get()
54 |
55 | for doc in docs:
56 | try:
57 | doc.reference.update({'sheetsConfig.row': str(new_row)})
58 | return True
59 | except Exception as e:
60 | print(f"Error updating user row: {e}")
61 | return False
62 |
63 | print(f"User with uid {uid} not found")
64 | return False
65 |
66 |
67 | def update_spreadsheet_id(username: str, spreadsheet_id: str):
68 | users_ref = db.collection('users')
69 | query = users_ref.where('username', '==', username).limit(1)
70 | docs = query.get()
71 |
72 | for doc in docs:
73 | try:
74 | doc.reference.update(
75 | {'sheetsConfig.spreadsheet_id': spreadsheet_id})
76 | print(f"Successfully updated spreadsheet_id for user {username}")
77 | return True
78 | except Exception as e:
79 | print(f"Error updating spreadsheet_id for user {username}: {e}")
80 | return False
81 |
82 | print(f"User {username} not found")
83 | return False
84 |
--------------------------------------------------------------------------------
/backend/initialize_sheet_agent.py:
--------------------------------------------------------------------------------
1 | import json
2 | from composio_crewai import Action, ComposioToolSet
3 | from crewai import Agent, Crew, Task, Process
4 | from langchain_openai import ChatOpenAI
5 | from dotenv import load_dotenv
6 | from crewai_tools.tools.base_tool import BaseTool
7 | from firebase.init import db
8 | import os
9 |
10 | load_dotenv()
11 | llm = ChatOpenAI(model="gpt-4o")
12 | from firebase.init import update_spreadsheet_id
13 | # get_user_by_username
14 |
15 |
16 | def createSheet(entityId: str):
17 | username = entityId
18 |
19 | def get_user_by_username(username):
20 | users_ref = db.collection('users')
21 | query = users_ref.where('username', '==', username).limit(1)
22 | docs = query.get()
23 |
24 | for doc in docs:
25 | return doc.to_dict()
26 |
27 | return False
28 |
29 | user = get_user_by_username(entityId)
30 | keywords = user['sheetsConfig']['keywords']
31 | attributes = user['sheetsConfig']['attributes']
32 | sheetTitle = user['sheetsConfig']['sheetTitle']
33 | email = user['email']
34 | composio_toolset = ComposioToolSet(
35 | api_key=os.environ.get("COMPOSIO_API_KEY"), entity_id=entityId)
36 |
37 | class UpdateSpreadsheetId(BaseTool):
38 | name: str = "Update Spreadsheet ID in the database"
39 | description: str = "This tool updates the spreadsheet_id for a user in the database"
40 |
41 | def _run(self, username: str, spreadsheet_id: str) -> str:
42 | success = update_spreadsheet_id(username, spreadsheet_id)
43 | if success:
44 | return "Spreadsheet ID updated successfully"
45 | else:
46 | return "Failed to update Spreadsheet ID"
47 |
48 | update_spreadsheet_id_tool = UpdateSpreadsheetId()
49 |
50 | google_tools = composio_toolset.get_actions(actions=[
51 | Action.GOOGLESHEETS_CREATE_GOOGLE_SHEET1,
52 | Action.GOOGLESHEETS_BATCH_UPDATE,
53 | Action.GMAIL_SEND_EMAIL
54 | ])
55 | google_tools.append(update_spreadsheet_id_tool)
56 |
57 | google_assistant = Agent(
58 | role="Gmail Assistant",
59 | goal= """
60 | 1. Create a Google Sheet and update it with provided values
61 | 2. Update the spreadsheet_id in the database.
62 | 3. Send an email to the user with specified subject and body.
63 | """,
64 | backstory=
65 | "You're an AI assistant that handles Google Sheets & Gmail operations using Google APIs.",
66 | verbose=True,
67 | llm=llm,
68 | tools=google_tools,
69 | allow_delegation=False,
70 | )
71 |
72 | create_google_sheet = Task(
73 | description=f"""
74 | 1. Create a new Google Sheet titled '{sheetTitle}', batch update it with the following values starting in row 1 ie from A1: {attributes} (if more than one, then update in the next column ex: A2, A3...),
75 | 2. Do not use includeValuesInResponse attribute.
76 | 3. Update the spreadsheet_id in the database for user with username: {username}
77 | 4. Send an email with recipient_email as: {email}, with subject {keywords} and body with dummy values for the following attributes: {attributes}. dont specify anything else apart from recipient_email, subject, body while sending email.
78 | """,
79 | agent=google_assistant,
80 | expected_output=
81 | "Create sheet, store values, update spreadsheet_id in the database, send an email to the user",
82 | )
83 |
84 | gmail_processing_crew = Crew(
85 | agents=[google_assistant],
86 | tasks=[create_google_sheet],
87 | verbose=1,
88 | process=Process.sequential,
89 | )
90 | return gmail_processing_crew.kickoff()
91 |
--------------------------------------------------------------------------------
/backend/main.py:
--------------------------------------------------------------------------------
1 | from fastapi import FastAPI, HTTPException, Request, Depends
2 | from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
3 | from pydantic import BaseModel
4 | from fastapi.middleware.cors import CORSMiddleware
5 | from firebase.init import auth
6 | from composio_config import createNewEntity, isEntityConnected, enable_gmail_trigger
7 | import logging
8 | from initialize_sheet_agent import createSheet
9 |
10 | logging.basicConfig(level=logging.INFO)
11 | logger = logging.getLogger(__name__)
12 |
13 | app = FastAPI()
14 |
15 | app.add_middleware(
16 | CORSMiddleware,
17 | allow_origins=["*"
18 | ], # Allows all origins, can be a list of specific origins
19 | allow_credentials=True,
20 | allow_methods=["*"], # Allows all methods (GET, POST, etc.)
21 | allow_headers=["*"], # Allows all headers
22 | )
23 |
24 |
25 | def verify_token(auth_credentials: HTTPAuthorizationCredentials = Depends(
26 | HTTPBearer())):
27 | token = auth_credentials.credentials
28 | try:
29 | decoded_token = auth.verify_id_token(token)
30 | return decoded_token
31 | except Exception:
32 | raise HTTPException(status_code=401, detail="Invalid or expired token")
33 |
34 |
35 | # Pydantic model for the request body
36 | class UserData(BaseModel):
37 | username: str
38 | appType: str
39 |
40 | class NewEntityData(BaseModel):
41 | username: str
42 | appType: str
43 | redirectUrl: str
44 |
45 | @app.post("/newentity")
46 | async def handle_request(user_data: NewEntityData,
47 | decoded_token: dict = Depends(verify_token)):
48 | user_id = decoded_token['uid']
49 | username = user_data.username
50 | appType = user_data.appType
51 | redirectUrl = user_data.redirectUrl
52 | res = createNewEntity(username, appType, redirectUrl)
53 | return res
54 |
55 |
56 | class EnableTriggerData(BaseModel):
57 | username: str
58 |
59 |
60 | @app.post("/enabletrigger")
61 | async def handle_request(user_data: EnableTriggerData,
62 | decoded_token: dict = Depends(verify_token)):
63 | user_id = decoded_token['uid']
64 | username = user_data.username
65 | res = enable_gmail_trigger(username)
66 | return res
67 |
68 |
69 | @app.post("/checkconnection")
70 | async def handle_request(user_data: UserData,
71 | decoded_token: dict = Depends(verify_token)):
72 | user_id = decoded_token['uid']
73 | username = user_data.username
74 | appType = user_data.appType
75 | res = isEntityConnected(username, appType)
76 | return res
77 |
78 |
79 | class UserData(BaseModel):
80 | username: str
81 |
82 |
83 | @app.post("/createsheet")
84 | async def handle_request(user_data: UserData,
85 | decoded_token: dict = Depends(verify_token)):
86 | username = user_data.username
87 | res = createSheet(username)
88 | return res
89 |
90 |
91 | @app.get("/")
92 | async def handle_request():
93 | return "ok"
94 |
95 |
96 | if __name__ == "__main__":
97 | import uvicorn
98 | uvicorn.run(app, host="0.0.0.0", port=8000)
99 |
100 | # Start the server (if running locally)
101 | # Run the following command in your terminal: uvicorn main:app --reload
102 |
--------------------------------------------------------------------------------
/backend/requirements.txt:
--------------------------------------------------------------------------------
1 | crewai
2 | composio-crewai
3 | langchain-openai
4 | python-dotenv
5 | crewai_tools
6 | fastapi
7 | uvicorn
8 | langchain_google_genai
9 |
10 |
--------------------------------------------------------------------------------
/backend/setup.sh:
--------------------------------------------------------------------------------
1 |
2 | #!/bin/bash
3 |
4 | # Create a virtual environment
5 | echo "Creating virtual environment..."
6 | python3 -m venv ~/.venvs/gmail_agent
7 |
8 | # Activate the virtual environment
9 | echo "Activating virtual environment..."
10 | source ~/.venvs/gmail_agent/bin/activate
11 |
12 | # Install libraries from requirements.txt
13 | echo "Installing libraries from requirements.txt..."
14 | pip install -r requirements.txt
15 |
16 | # Login to your account
17 | echo "Login to your Composio acount"
18 | composio login
19 |
20 | # Add calendar tool
21 | echo "Add Gmail tools. Finish the flow"
22 | composio add gmail
23 | composio add googlesheets
24 |
25 | # Copy env backup to .env file
26 | if [ -f ".env.example" ]; then
27 | echo "Copying .env.example to .env..."
28 | cp .env.example .env
29 | else
30 | echo "No .env.example file found. Creating a new .env file..."
31 | touch .env
32 | fi
33 |
34 | # Prompt user to fill the .env file
35 | echo "Please fill in the .env file with the necessary environment variables."
36 |
37 | echo "Setup completed successfully!"
38 |
--------------------------------------------------------------------------------
/eslint.config.js:
--------------------------------------------------------------------------------
1 | import js from '@eslint/js'
2 | import globals from 'globals'
3 | import react from 'eslint-plugin-react'
4 | import reactHooks from 'eslint-plugin-react-hooks'
5 | import reactRefresh from 'eslint-plugin-react-refresh'
6 |
7 | export default [
8 | { ignores: ['dist'] },
9 | {
10 | files: ['**/*.{js,jsx}'],
11 | languageOptions: {
12 | ecmaVersion: 2020,
13 | globals: globals.browser,
14 | parserOptions: {
15 | ecmaVersion: 'latest',
16 | ecmaFeatures: { jsx: true },
17 | sourceType: 'module',
18 | },
19 | },
20 | settings: { react: { version: '18.3' } },
21 | plugins: {
22 | react,
23 | 'react-hooks': reactHooks,
24 | 'react-refresh': reactRefresh,
25 | },
26 | rules: {
27 | ...js.configs.recommended.rules,
28 | ...react.configs.recommended.rules,
29 | ...react.configs['jsx-runtime'].rules,
30 | ...reactHooks.configs.recommended.rules,
31 | 'react/jsx-no-target-blank': 'off',
32 | 'react-refresh/only-export-components': [
33 | 'warn',
34 | { allowConstantExport: true },
35 | ],
36 | },
37 | },
38 | ]
39 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
11 | {body} 12 |
13 | 14 | ); 15 | }; 16 | 17 | export default BenefitCard; -------------------------------------------------------------------------------- /src/components/Benefits.jsx: -------------------------------------------------------------------------------- 1 | import BenefitCard from "./BenefitCard"; 2 | 3 | const benefits = [ 4 | { 5 | "title": "Time-Saving", 6 | "body": "Eliminate the need for manual email searching and attachment management, freeing up valuable time for other tasks" 7 | }, 8 | { 9 | "title": "Improved Productivity", 10 | "body": "Focus on important projects while Attachments Extractor handles the data extraction process, boosting overall productivity" 11 | }, 12 | { 13 | "title": "Enhanced Organization", 14 | "body": "Keep valuable information neatly organized in a spreadsheet, making it easy to access and reference whenever needed" 15 | }, 16 | { 17 | "title": "Informed Decision Making", 18 | "body": "Quickly access key data extracted from attachments to make well-informed decisions that drive success" 19 | } 20 | ] 21 | 22 | const Benefits = () => { 23 | return{faq.answer}
76 |Automatically processes new emails, extracts data from attachments, and organizes everything in a spreadsheet!
8 |This page looks better on Desktop
8 |Please view on a larger screen for the best experience.
9 |20 | Subject 21 | | 22 |23 | Attachment 24 | | 25 |
---|---|
Apple Tv Invoice | 30 |
31 |
32 |
35 | |
36 |
Composio Invoice | 39 |
40 |
41 |
44 | |
45 |
71 | Invoice # 72 | | 73 |74 | Amount 75 | | 76 |77 | Due Date 78 | | 79 |
---|---|---|
AP24-8291 | 84 |$1099.41 | 85 |2024-09-30 | 86 |
CMP-5721364 | 89 |$30.00 | 90 |2024-08-01 | 91 |
47 | // Sheet ID: {userDetails.sheetsConfig.spreadsheet_id} 48 | //
49 | //{userDetails?.sheetsConfig?.keywords || "No keywords configured"}
62 | //{userDetails?.sheetsConfig?.attributes || "No attributes configured"}
66 | //You're logged out, login below
15 | 38 |Something's missing.
10 |Sorry, we can't find that page.
11 | Back to Homepage 12 |