diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..4db3611 Binary files /dev/null and b/.DS_Store differ diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 9d3de47..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2025 AgentStack - -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/README.md b/README.md deleted file mode 100644 index 79c2c9a..0000000 --- a/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# AgentStack Example Agents - -Every project is starts with `agentstack init` and runs with `agentstack run` - -## Agents -- []() \ No newline at end of file diff --git a/agentstack.log b/agentstack.log new file mode 100644 index 0000000..598c24c --- /dev/null +++ b/agentstack.log @@ -0,0 +1,3 @@ +ERROR: An error occurred: +Could not find agentstack.json, are you in an AgentStack project directory? +INFO: Run again with --debug for more information. diff --git a/pitchdeck_analyzer/.DS_Store b/pitchdeck_analyzer/.DS_Store new file mode 100644 index 0000000..de6e6f8 Binary files /dev/null and b/pitchdeck_analyzer/.DS_Store differ diff --git a/pitchdeck_analyzer/.env.example b/pitchdeck_analyzer/.env.example new file mode 100644 index 0000000..355bd1b --- /dev/null +++ b/pitchdeck_analyzer/.env.example @@ -0,0 +1,12 @@ +#AGENTOPS_API_KEY=... +#OPENAI_API_KEY=... + +# Tools +#COMPOSIO_API_KEY="" + +# API Keys +CHATPDF_API_KEY=your_chatpdf_api_key_here +OPENAI_API_KEY=your_openai_api_key_here + +# Google Sheets Configuration (Can also be added to the configuration.json file) +# GOOGLE_SHEETS_ID=your_google_sheets_id_here \ No newline at end of file diff --git a/.gitignore b/pitchdeck_analyzer/.gitignore similarity index 91% rename from .gitignore rename to pitchdeck_analyzer/.gitignore index 15201ac..154286d 100644 --- a/.gitignore +++ b/pitchdeck_analyzer/.gitignore @@ -94,12 +94,6 @@ ipython_config.py # install all needed dependencies. #Pipfile.lock -# UV -# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. -# This is especially recommended for binary packages to ensure reproducibility, and is more -# commonly ignored for libraries. -#uv.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 @@ -167,5 +161,31 @@ cython_debug/ # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ -# PyPI configuration file -.pypirc +.agentops/ + +# Virtual Environment +venv/ +.env +.venv +ENV/ + +# IDE +.idea/ +.vscode/ +*.swp +*.swo + +# Credentials and tokens +src/tools/credentials.json +src/token.json +token.json +**/token.json +*.token +*.credentials +*.key + +# Downloaded files +attachment_*.pdf + +# Logs +*.log diff --git a/pitchdeck_analyzer/LICENSE.md b/pitchdeck_analyzer/LICENSE.md new file mode 100644 index 0000000..41752f3 --- /dev/null +++ b/pitchdeck_analyzer/LICENSE.md @@ -0,0 +1,10 @@ + +MIT License + +Copyright (c) 2025 Name + +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. \ No newline at end of file diff --git a/pitchdeck_analyzer/README.md b/pitchdeck_analyzer/README.md new file mode 100644 index 0000000..f268475 --- /dev/null +++ b/pitchdeck_analyzer/README.md @@ -0,0 +1,37 @@ +# pitchdeck_analyzer +This is the start of your AgentStack project. + +## How to build your Crew Agent +### With the CLI +Add an agent using AgentStack with the CLI: +`agentstack generate agent ` +You can also shorten this to `agentstack g a ` +For wizard support use `agentstack g a --wizard` +Finally for creation in the CLI alone, use `agentstack g a --role/-r --goal/-g --backstory/-b --model/-m ` + +This will automatically create a new agent in the `agents.yaml` config as well as in your code. Either placeholder strings will be used, or data included in the wizard. + +Similarly, tasks can be created with `agentstack g t ` + +Add tools with `agentstack tools add` and view tools available with `agentstack tools list` + +## How to use your Agent +In this directory, run `uv pip install --requirements pyproject.toml` + +To run your project, use the following command: +`agentstack run` + +This will initialize your crew of AI agents and begin task execution as defined in your configuration in the main.py file. + +#### Replay Tasks from Latest Crew Kickoff: + +CrewAI now includes a replay feature that allows you to list the tasks from the last run and replay from a specific one. To use this feature, run: +`crewai replay ` +Replace with the ID of the task you want to replay. + +#### Reset Crew Memory +If you need to reset the memory of your crew before running it again, you can do so by calling the reset memory feature: +`crewai reset-memory` +This will clear the crew's memory, allowing for a fresh start. + +> 🪩 Project built with [AgentStack](https://github.com/AgentOps-AI/AgentStack) \ No newline at end of file diff --git a/pitchdeck_analyzer/agentstack.json b/pitchdeck_analyzer/agentstack.json new file mode 100644 index 0000000..8b098bf --- /dev/null +++ b/pitchdeck_analyzer/agentstack.json @@ -0,0 +1,10 @@ +{ + "framework": "crewai", + "tools": [ + "file_read", + "composio" + ], + "agentstack_version": "0.3.0", + "template": "hello_alex", + "template_version": "4" +} \ No newline at end of file diff --git a/pitchdeck_analyzer/pyproject.toml b/pitchdeck_analyzer/pyproject.toml new file mode 100644 index 0000000..9ba47b6 --- /dev/null +++ b/pitchdeck_analyzer/pyproject.toml @@ -0,0 +1,13 @@ +[project] +name = "pitchdeck_analyzer" +version = "0.0.1" +description = "This is the start of your AgentStack project." +authors = [ + { name = "Name " } +] +license = { text = "MIT" } +requires-python = ">=3.10" + +dependencies = [ + "agentstack[crewai]>=0.3.0", +] \ No newline at end of file diff --git a/pitchdeck_analyzer/src/.DS_Store b/pitchdeck_analyzer/src/.DS_Store new file mode 100644 index 0000000..65243e5 Binary files /dev/null and b/pitchdeck_analyzer/src/.DS_Store differ diff --git a/pitchdeck_analyzer/src/__init__.py b/pitchdeck_analyzer/src/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/pitchdeck_analyzer/src/config/agents.yaml b/pitchdeck_analyzer/src/config/agents.yaml new file mode 100644 index 0000000..40a0ca3 --- /dev/null +++ b/pitchdeck_analyzer/src/config/agents.yaml @@ -0,0 +1,8 @@ +email_agent: + role: >- + You are an expert at processing emails and handling attachments. + goal: >- + Fetch recent emails and download any PDF attachments to a local folder. + backstory: >- + You are specialized in email processing and file handling, with a focus on efficiently managing attachments. + llm: openai/gpt-4 diff --git a/pitchdeck_analyzer/src/config/inputs.yaml b/pitchdeck_analyzer/src/config/inputs.yaml new file mode 100644 index 0000000..e69de29 diff --git a/pitchdeck_analyzer/src/config/tasks.yaml b/pitchdeck_analyzer/src/config/tasks.yaml new file mode 100644 index 0000000..a5be91c --- /dev/null +++ b/pitchdeck_analyzer/src/config/tasks.yaml @@ -0,0 +1,10 @@ +fetch_emails: + description: >- + Check the last 5 emails in the inbox: + 1. Print email details (subject, sender, content) + 2. If email has PDF attachment, download it to local downloads folder + 3. Skip emails without PDF attachments + 4. Report number of PDFs downloaded + expected_output: >- + A summary of processed emails and any downloaded PDFs. + agent: email_agent diff --git a/pitchdeck_analyzer/src/crew.py b/pitchdeck_analyzer/src/crew.py new file mode 100644 index 0000000..a0eba30 --- /dev/null +++ b/pitchdeck_analyzer/src/crew.py @@ -0,0 +1,185 @@ +from crewai import Agent, Crew, Process, Task +from crewai.project import CrewBase, agent, crew, task +from tools.fetch_emails import get_message_content +from tools.pdf_reader import upload_pdf, ask_question +from tools.write_to_docs import append_row +import os +import base64 +from googleapiclient.discovery import build +from google.oauth2.credentials import Credentials +from google.auth.transport.requests import Request +from google_auth_oauthlib.flow import InstalledAppFlow +from dotenv import load_dotenv + +# Load environment variables from .env file +load_dotenv() + +@CrewBase +class EmailFetcherCrew(): + """Crew for fetching emails, processing PDFs, and updating spreadsheets""" + + @task + def process_emails(self) -> Task: + """Process emails, download PDFs, extract data, and update spreadsheet.""" + self.get_emails() + return Task( + description=""" + 1. Check the last 5 emails in the inbox + 2. Download PDF attachments + 3. Extract required information from PDFs + 4. Append extracted data to the spreadsheet + """, + expected_output=""" + A summary of processed emails, extracted data, and spreadsheet updates. + """, + agent=self.email_agent() + ) + + def get_emails(self): + """Get the last 5 emails from Gmail and process attachments.""" + try: + SCOPES = [ + 'https://www.googleapis.com/auth/spreadsheets', + 'https://www.googleapis.com/auth/gmail.readonly' + ] + creds = None + token_path = "src/token.json" + + if os.path.exists(token_path): + creds = Credentials.from_authorized_user_file(token_path, SCOPES) + if not creds or not creds.valid: + if creds and creds.expired and creds.refresh_token: + creds.refresh(Request()) + else: + flow = InstalledAppFlow.from_client_secrets_file( + "src/tools/credentials.json", SCOPES) + creds = flow.run_local_server(port=0) + with open(token_path, "w") as token: + token.write(creds.to_json()) + + service = build("gmail", "v1", credentials=creds) + results = service.users().messages().list(userId='me', maxResults=5).execute() + messages = results.get('messages', []) + + for message in messages: + msg = service.users().messages().get(userId='me', id=message['id'], format='full').execute() + headers = msg['payload']['headers'] + subject = next((h['value'] for h in headers if h['name'].lower() == 'subject'), 'No Subject') + sender = next((h['value'] for h in headers if h['name'].lower() == 'from'), 'No Sender') + + print(f"\nEmail from: {sender}") + print(f"Subject: {subject}") + + if 'parts' in msg['payload']: + for part in msg['payload']['parts']: + if part.get('filename', '').endswith('.pdf'): + attachment_id = part['body'].get('attachmentId') + if attachment_id: + file_path = self.download_attachment(service, message['id'], attachment_id) + if file_path: + self.process_pdf(file_path) + + except Exception as e: + print(f"Error accessing emails: {str(e)}") + + def download_attachment(self, service, message_id: str, attachment_id: str) -> str: + """Download a PDF attachment from an email.""" + try: + attachment = service.users().messages().attachments().get( + userId='me', messageId=message_id, id=attachment_id).execute() + + file_data = base64.urlsafe_b64decode(attachment['data'].encode('UTF-8')) + + # Save to current directory + file_path = f'attachment_{message_id}.pdf' + with open(file_path, 'wb') as f: + f.write(file_data) + + print(f"Downloaded PDF: {file_path}") + return file_path + + except Exception as e: + print(f"Error downloading attachment: {str(e)}") + return None + + def process_pdf(self, pdf_path: str): + """Process the PDF to extract information and update the spreadsheet.""" + try: + api_key = os.getenv("CHATPDF_API_KEY") + if not api_key: + print("ChatPDF API key not found. Please set the CHATPDF_API_KEY in your .env file.") + return + + # Ensure the correct path is used + pdf_path = os.path.abspath(pdf_path) + print(f"Processing PDF: {pdf_path}") + + source_id = upload_pdf(api_key, pdf_path) + if not source_id: + print("Failed to upload PDF.") + return + + print("PDF uploaded successfully. Extracting information...") + + questions = [ + "Who is the main team behind this company? Could be CTO, CEO, Co-Founder, Founder, Investor, etc. Definitely find upto 1 person. Only respond with name and title", + "Who is the second main team behind this company? Could be CTO, CEO, Co-Founder, Founder, Investor, etc. Definitely find upto 1 person. Only respond with name and title", + "Who is the third main team behind this company? Could be CTO, CEO, Co-Founder, Founder, Investor, etc. Definitely find upto 1 person. Only respond with name and title", + "What is the company name?", + "What is the main business idea or summary?", + "What industry or sector is this company in?", + "How much funding are they asking for (the ask amount)?", + "Who are the competitors of this company and their details? What are other similar companies in the space and how are they doing? What are their names, revenue, valuation, etc?", + ] + + results = {} + for question in questions: + print(f"\nAsking: {question}") + answer = ask_question(api_key, source_id, question) + if answer: + results[question] = answer + print(f"Answer: {answer}") + else: + print(f"Failed to get answer for: {question}") + results[question] = "" + + print("\nUpdating spreadsheet with extracted information...") + success = append_row( + founder1=results.get("Who is the main team behind this company? Could be CTO, CEO, Co-Founder, Founder, Investor, etc. Definitely find upto 1 person. Only respond with name and title", ""), + founder2=results.get("Who is the second main team behind this company? Could be CTO, CEO, Co-Founder, Founder, Investor, etc. Definitely find upto 1 person. Only respond with name and title", ""), + founder3=results.get("Who is the third main team behind this company? Could be CTO, CEO, Co-Founder, Founder, Investor, etc. Definitely find upto 1 person. Only respond with name and title", ""), + company_name=results.get("What is the company name?", ""), + idea_summary=results.get("What is the main business idea or summary?", ""), + industry=results.get("What industry or sector is this company in?", ""), + ask=results.get("How much funding are they asking for (the ask amount)?", ""), + competitors=results.get("Who are the competitors of this company?", ""), + ) + + if success: + print("Spreadsheet updated successfully.") + else: + print("Failed to update spreadsheet.") + + except Exception as e: + print(f"Error processing PDF: {str(e)}") + + @agent + def email_agent(self) -> Agent: + return Agent( + name="EmailAgent", + role="Email Processing Specialist", + description="Expert at fetching emails, processing PDFs, and updating spreadsheets.", + goal="Process recent emails, extract data from PDFs, and update spreadsheets.", + backstory="An agent specialized in email processing and data extraction.", + verbose=True + ) + + @crew + def crew(self) -> Crew: + """Creates the email processing crew""" + return Crew( + agents=[self.email_agent()], + tasks=[self.process_emails()], + process=Process.sequential, + verbose=True + ) \ No newline at end of file diff --git a/pitchdeck_analyzer/src/main.py b/pitchdeck_analyzer/src/main.py new file mode 100644 index 0000000..2ba0ed0 --- /dev/null +++ b/pitchdeck_analyzer/src/main.py @@ -0,0 +1,58 @@ +#!/usr/bin/env python + +import sys +from crew import EmailFetcherCrew +import agentstack +import agentops + +agentops.init(default_tags=agentstack.get_tags()) + +instance = EmailFetcherCrew().crew() + +def run(): + """ + Run the agent. + """ + instance.kickoff(inputs=agentstack.get_inputs()) + + +def train(): + """ + Train the crew for a given number of iterations. + """ + try: + instance.train( + n_iterations=int(sys.argv[1]), + filename=sys.argv[2], + inputs=agentstack.get_inputs(), + ) + except Exception as e: + raise Exception(f"An error occurred while training the crew: {e}") + + +def replay(): + """ + Replay the crew execution from a specific task. + """ + try: + instance.replay(task_id=sys.argv[1]) + except Exception as e: + raise Exception(f"An error occurred while replaying the crew: {e}") + + +def test(): + """ + Test the crew execution and returns the results. + """ + try: + instance.test( + n_iterations=int(sys.argv[1]), + openai_model_name=sys.argv[2], + inputs=agentstack.get_inputs(), + ) + except Exception as e: + raise Exception(f"An error occurred while replaying the crew: {e}") + + +if __name__ == '__main__': + run() \ No newline at end of file diff --git a/pitchdeck_analyzer/src/token.json.example b/pitchdeck_analyzer/src/token.json.example new file mode 100644 index 0000000..83b6acf --- /dev/null +++ b/pitchdeck_analyzer/src/token.json.example @@ -0,0 +1,14 @@ +{ + "token":..., + "refresh_token":..., + "token_uri":..., + "client_id":..., + "client_secret":..., + "scopes": [ + "https://www.googleapis.com/auth/spreadsheets", + "https://www.googleapis.com/auth/gmail.readonly", + ], + "universe_domain": "googleapis.com", + "account": "", + "expiry":... +} \ No newline at end of file diff --git a/pitchdeck_analyzer/src/tools/__init__.py b/pitchdeck_analyzer/src/tools/__init__.py new file mode 100644 index 0000000..3feeed1 --- /dev/null +++ b/pitchdeck_analyzer/src/tools/__init__.py @@ -0,0 +1,6 @@ +from .fetch_emails import get_message_content +from .pdf_reader import upload_pdf, ask_question +from .write_to_docs import append_row + +# Export the functions directly +__all__ = ['get_message_content', 'upload_pdf', 'ask_question', 'append_row'] \ No newline at end of file diff --git a/pitchdeck_analyzer/src/tools/credentials.json.example b/pitchdeck_analyzer/src/tools/credentials.json.example new file mode 100644 index 0000000..7f50b9e --- /dev/null +++ b/pitchdeck_analyzer/src/tools/credentials.json.example @@ -0,0 +1,13 @@ +{ + "installed": { + "client_id":..., + "project_id":..., + "auth_uri":..., + "token_uri":..., + "auth_provider_x509_cert_url":..., + "client_secret":..., + "redirect_uris": [ + "http://localhost" + ] + } +} \ No newline at end of file diff --git a/pitchdeck_analyzer/src/tools/fetch_emails.py b/pitchdeck_analyzer/src/tools/fetch_emails.py new file mode 100644 index 0000000..7512581 --- /dev/null +++ b/pitchdeck_analyzer/src/tools/fetch_emails.py @@ -0,0 +1,107 @@ +import os.path +import base64 +from email.mime.text import MIMEText + +from google.auth.transport.requests import Request +from google.oauth2.credentials import Credentials +from google_auth_oauthlib.flow import InstalledAppFlow +from googleapiclient.discovery import build +from googleapiclient.errors import HttpError + +# If modifying these scopes, delete the file token.json. +SCOPES = [ + "https://www.googleapis.com/auth/gmail.readonly", + "https://www.googleapis.com/auth/gmail.modify" +] + +# Get the directory where this script is located +SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) +# Path to credentials relative to script location +CREDENTIALS_PATH = os.path.join(os.path.dirname(SCRIPT_DIR), "credentials.json") +TOKEN_PATH = os.path.join(os.path.dirname(SCRIPT_DIR), "token.json") + +def get_message_content(service, msg_id): + """Get the content of a specific message.""" + try: + message = service.users().messages().get(userId='me', id=msg_id, format='full').execute() + + # Get headers + headers = message['payload']['headers'] + subject = next((header['value'] for header in headers if header['name'].lower() == 'subject'), 'No Subject') + from_email = next((header['value'] for header in headers if header['name'].lower() == 'from'), 'No Sender') + + # Get message body + if 'parts' in message['payload']: + parts = message['payload']['parts'] + body = '' + has_pdf = False + + for part in parts: + if part['mimeType'] == 'text/plain': + if 'data' in part['body']: + body = base64.urlsafe_b64decode(part['body']['data']).decode('utf-8') + elif part['mimeType'] == 'application/pdf': + has_pdf = True + # Check for PDF in attachments + if 'filename' in part and part['filename'].lower().endswith('.pdf'): + has_pdf = True + else: + # Handle messages with no parts + if 'body' in message['payload'] and 'data' in message['payload']['body']: + body = base64.urlsafe_b64decode(message['payload']['body']['data']).decode('utf-8') + else: + body = 'No content' + has_pdf = False + + return { + 'subject': subject, + 'from': from_email, + 'body': body, + 'has_pdf': has_pdf + } + except Exception as e: + print(f"Error processing message {msg_id}: {str(e)}") + return None + +def main(): + """Fetch last 5 emails and their content.""" + creds = None + if os.path.exists(TOKEN_PATH): + creds = Credentials.from_authorized_user_file(TOKEN_PATH, SCOPES) + if not creds or not creds.valid: + if creds and creds.expired and creds.refresh_token: + creds.refresh(Request()) + else: + flow = InstalledAppFlow.from_client_secrets_file( + CREDENTIALS_PATH, SCOPES + ) + creds = flow.run_local_server(port=0) + with open(TOKEN_PATH, "w") as token: + token.write(creds.to_json()) + + try: + service = build("gmail", "v1", credentials=creds) + + # Get list of messages + results = service.users().messages().list(userId='me', maxResults=5).execute() + messages = results.get('messages', []) + + if not messages: + print("No messages found.") + return + + print("\nFetching last 5 emails:\n") + for message in messages: + msg_data = get_message_content(service, message['id']) + if msg_data: + print(f"\nSubject: {msg_data['subject']}") + print(f"From: {msg_data['from']}") + print(f"Has PDF Attachment: {'Yes' if msg_data['has_pdf'] else 'No'}") + print(f"Content Preview: {msg_data['body'][:200]}...") + print("-" * 80) + + except HttpError as error: + print(f"An error occurred: {error}") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/pitchdeck_analyzer/src/tools/pdf_reader.py b/pitchdeck_analyzer/src/tools/pdf_reader.py new file mode 100644 index 0000000..680fe69 --- /dev/null +++ b/pitchdeck_analyzer/src/tools/pdf_reader.py @@ -0,0 +1,112 @@ +import os +import requests +from dotenv import load_dotenv + +# Load environment variables from .env file +load_dotenv() + +def upload_pdf(api_key: str, pdf_path: str): + """Upload a PDF file and return the source ID.""" + if not os.path.exists(pdf_path): + print(f"Error: File not found at {pdf_path}") + return None + + try: + with open(pdf_path, 'rb') as file: + files = {'file': ('file.pdf', file, 'application/octet-stream')} + + response = requests.post( + 'https://api.chatpdf.com/v1/sources/add-file', + headers={'x-api-key': api_key}, + files=files + ) + + if response.status_code == 200: + data = response.json() + source_id = data.get('sourceId') + if source_id: + print('Source ID:', source_id) + return source_id + else: + print('Error: Missing "sourceId" in response') + print('Response:', response.text) + else: + print('Status:', response.status_code) + print('Error:', response.text) + except requests.exceptions.RequestException as e: + print(f"Request Error: {str(e)}") + except Exception as e: + print(f"Unexpected Error: {str(e)}") + + return None + + +def ask_question(api_key: str, source_id: str, question: str) -> str: + """Ask a single question about the PDF.""" + headers = { + 'x-api-key': api_key, + 'Content-Type': 'application/json' + } + + # Format the question to request concise answers + formatted_question = f"Provide a direct, concise answer. If the information is not found in the document, respond with 'None'. {question}" + + data = { + 'sourceId': source_id, + 'messages': [{'role': 'user', 'content': formatted_question}] + } + + try: + response = requests.post( + 'https://api.chatpdf.com/v1/chats/message', + headers=headers, + json=data + ) + + if response.status_code == 200: + json_response = response.json() + answer = json_response.get('content', '').strip() + # If the answer indicates no information was found, return None + if any(phrase in answer.lower() for phrase in [ + "cannot find", "no information", "not mentioned", + "not specified", "not found", "not provided" + ]): + return "None" + return answer + else: + print('Status:', response.status_code) + print('Error:', response.text) + return "None" + except requests.exceptions.RequestException as e: + print(f"Request Error: {str(e)}") + return "None" + except Exception as e: + print(f"Unexpected Error: {str(e)}") + return "None" + + +def main(): + # Use the ChatPDF API key from the environment + API_KEY = os.getenv("CHATPDF_API_KEY") + if not API_KEY: + print("ChatPDF API key not found. Please set the CHATPDF_API_KEY in your .env file.") + return + + pdf_path = os.path.abspath("src/your_pdf.pdf") + + source_id = upload_pdf(API_KEY, pdf_path) + if not source_id: + print("Failed to upload PDF. Exiting.") + return + + questions = ["What is Elon Musk most known for?", "What are the key achievements of Elon Musk?"] + for question in questions: + result = ask_question(API_KEY, source_id, question) + if result: + print('Result:', result) + else: + print(f"Failed to get a response from ChatPDF for the question: {question}") + + +if __name__ == "__main__": + main() diff --git a/pitchdeck_analyzer/src/tools/write_to_docs.py b/pitchdeck_analyzer/src/tools/write_to_docs.py new file mode 100644 index 0000000..c52e3bf --- /dev/null +++ b/pitchdeck_analyzer/src/tools/write_to_docs.py @@ -0,0 +1,155 @@ +from google.oauth2.credentials import Credentials +from google_auth_oauthlib.flow import InstalledAppFlow +from google.auth.transport.requests import Request +from googleapiclient.discovery import build +import os.path +from typing import Optional, List, Union + +# If modifying these scopes, delete the token.json file. +SCOPES = [ + 'https://www.googleapis.com/auth/spreadsheets', + 'https://www.googleapis.com/auth/gmail.readonly' +] + +# Column structure matching the spreadsheet +COLUMNS = [ + 'Founder Name 1', + 'Founder Name 2', + 'Founder Name 3', + 'Company Name', + 'Summary of Idea', + 'Industry/Sector', + 'Ask', + 'Competitors' +] + +def get_google_sheets_service(): + """Gets Google Sheets service with appropriate credentials.""" + creds = None + # The file token.json stores the user's access and refresh tokens + token_path = "src/token.json" + + if os.path.exists(token_path): + try: + os.remove(token_path) # Remove existing token to force new authentication + print("Removed existing token to refresh permissions.") + except: + pass + + if os.path.exists(token_path): + creds = Credentials.from_authorized_user_file(token_path, SCOPES) + + # If there are no (valid) credentials available, let the user log in. + if not creds or not creds.valid: + if creds and creds.expired and creds.refresh_token: + creds.refresh(Request()) + else: + flow = InstalledAppFlow.from_client_secrets_file( + 'src/tools/credentials.json', SCOPES) + creds = flow.run_local_server(port=0) + # Save the credentials for the next run + with open(token_path, 'w') as token: + token.write(creds.to_json()) + + try: + service = build('sheets', 'v4', credentials=creds) + return service + except Exception as e: + print(f"Error building Google Sheets service: {str(e)}") + return None + +def append_row( + founder1: Optional[str] = None, + founder2: Optional[str] = None, + founder3: Optional[str] = None, + company_name: Optional[str] = None, + idea_summary: Optional[str] = None, + industry: Optional[str] = None, + ask: Optional[str] = None, + competitors: Optional[str] = None, +) -> bool: + """ + Appends a row to the specified spreadsheet with the given values. + Any None values will be added as empty cells. + + Args: + founder1 (str, optional): Name of first founder + founder2 (str, optional): Name of second founder + founder3 (str, optional): Name of third founder + company_name (str, optional): Name of the company + idea_summary (str, optional): Summary of the business idea + industry (str, optional): Industry/Sector + ask (str, optional): Ask amount + competitors (str, optional): Competitors and their details + + Returns: + bool: True if successful, False otherwise + """ + try: + SPREADSHEET_ID = '1oOMFd-gijhkDmeYeVRSlvoQaQf5geolLV-N7MdIdYfY' + RANGE_NAME = 'Sheet1!A:K' # A to K for 11 columns + + service = get_google_sheets_service() + if not service: + print("Failed to get Google Sheets service") + return False + + # Create row data with empty strings for None values + row_data = [ + founder1 or '', + founder2 or '', + founder3 or '', + company_name or '', + idea_summary or '', + industry or '', + ask or '', + competitors or '', + ] + + # Prepare the data for appending + values = [row_data] + body = { + 'values': values + } + + try: + # Append the row + result = service.spreadsheets().values().append( + spreadsheetId=SPREADSHEET_ID, + range=RANGE_NAME, + valueInputOption='USER_ENTERED', + insertDataOption='INSERT_ROWS', + body=body + ).execute() + + updated_rows = result.get('updates', {}).get('updatedRows', 0) + print(f"Row appended successfully: {updated_rows} rows added.") + return True + except Exception as e: + print(f"Error appending to spreadsheet: {str(e)}") + return False + + except Exception as e: + print(f"Error in append_row: {str(e)}") + return False + +def main(): + # Example usage with some null values + success = append_row( + founder1="John Doe", + founder2="Jane Smith", + # founder3 is None/empty + company_name="Tech Startup Inc.", + idea_summary="AI-powered analytics platform", + industry="Technology", + ask="$2M", + competitors="Competitor 1, Competitor 2, Competitor 3" + ) + + if success: + print("Row added successfully!") + else: + print("Failed to add row.") + +if __name__ == '__main__': + main()