28 | );
29 | };
30 |
--------------------------------------------------------------------------------
/nextjs_app/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/nextjs_app/README.md:
--------------------------------------------------------------------------------
1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
2 |
3 | ## Getting Started
4 |
5 | First, run the development server:
6 |
7 | ```bash
8 | npm run dev
9 | # or
10 | yarn dev
11 | # or
12 | pnpm dev
13 | # or
14 | bun dev
15 | ```
16 |
17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
18 |
19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
20 |
21 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
22 |
23 | ## Learn More
24 |
25 | To learn more about Next.js, take a look at the following resources:
26 |
27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
29 |
30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
31 |
32 | ## Deploy on Vercel
33 |
34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
35 |
36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
37 |
--------------------------------------------------------------------------------
/nextjs_app/app/page.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import { EventLog } from "@/components/EventLog";
4 | import { FinalOutput } from "@/components/FinalOutput";
5 | import InputSection from "@/components/InputSection";
6 | import { useCrewJob } from "@/hooks/useCrewJob";
7 |
8 | export default function Home() {
9 | // Hooks
10 | const crewJob = useCrewJob();
11 |
12 | return (
13 |
82 | );
83 | };
84 |
--------------------------------------------------------------------------------
/crewai_be/tasks.py:
--------------------------------------------------------------------------------
1 | from crewai import Task, Agent
2 | from textwrap import dedent
3 |
4 |
5 | from job_manager import append_event
6 | from models import PositionInfo, PositionInfoList
7 | from utils.logging import logger
8 |
9 |
10 | class CompanyResearchTasks():
11 |
12 | def __init__(self, job_id):
13 | self.job_id = job_id
14 |
15 | def append_event_callback(self, task_output):
16 | logger.info("Callback called: %s", task_output)
17 | append_event(self.job_id, task_output.exported_output)
18 |
19 | def manage_research(self, agent: Agent, companies: list[str], positions: list[str], tasks: list[Task]):
20 | return Task(
21 | description=dedent(f"""Based on the list of companies {companies} and the positions {positions},
22 | use the results from the Company Research Agent to research each position in each company.
23 | to put together a json object containing the URLs for 3 blog articles, the URLs and title
24 | for 3 YouTube interviews for each position in each company.
25 |
26 | """),
27 | agent=agent,
28 | expected_output=dedent(
29 | """A json object containing the URLs for 3 blog articles and the URLs and
30 | titles for 3 YouTube interviews for each position in each company."""),
31 | callback=self.append_event_callback,
32 | context=tasks,
33 | output_json=PositionInfoList
34 | )
35 |
36 | def company_research(self, agent: Agent, company: str, positions: list[str]):
37 | return Task(
38 | description=dedent(f"""Research the position {positions} for the {company} company.
39 | For each position,
40 |
41 | nd the URLs for 3 recent blog articles and the URLs and titles for
42 | 3 recent YouTube interviews for the person in each position.
43 | Return this collected information in a JSON object.
44 |
45 | Helpful Tips:
46 | - To find the blog articles names and URLs, perform searches on Google such like the following:
47 | - "{company} [POSITION HERE] blog articles"
48 | - To find the youtube interviews, perform searches on YouTube such as the following:
49 | - "{company} [POSITION HERE] interview"
50 |
51 | Important:
52 | - Once you've found the information, immediately stop searching for additional information.
53 | - Only return the requested information. NOTHING ELSE!
54 | - Do not generate fake information. Only return the information you find. Nothing else!
55 | - Do not stop researching until you find the requested information for each position in the company.
56 | """),
57 | agent=agent,
58 | expected_output="""A JSON object containing the researched information for each position in the company.""",
59 | callback=self.append_event_callback,
60 | output_json=PositionInfo,
61 | async_execution=True
62 | )
63 |
--------------------------------------------------------------------------------
/crewai_be/agents.py:
--------------------------------------------------------------------------------
1 | from typing import List
2 | from crewai import Agent
3 | from langchain_openai import ChatOpenAI
4 | from crewai_tools import SerperDevTool
5 | from tools.youtube_search_tools import YoutubeVideoSearchTool
6 |
7 |
8 | class CompanyResearchAgents():
9 |
10 | def __init__(self):
11 | self.searchInternetTool = SerperDevTool()
12 | self.youtubeSearchTool = YoutubeVideoSearchTool()
13 | self.llm = ChatOpenAI(model="gpt-4-turbo-preview")
14 |
15 | def research_manager(self, companies: List[str], positions: List[str]) -> Agent:
16 | return Agent(
17 | role="Company Research Manager",
18 | goal=f"""Generate a list of JSON objects containing the urls for 3 recent blog articles and
19 | the url and title for 3 recent YouTube interview, for each position in each company.
20 |
21 | Companies: {companies}
22 | Positions: {positions}
23 |
24 | Important:
25 | - The final list of JSON objects must include all companies and positions. Do not leave any out.
26 | - If you can't find information for a specific position, fill in the information with the word "MISSING".
27 | - Do not generate fake information. Only return the information you find. Nothing else!
28 | - Do not stop researching until you find the requested information for each position in each company.
29 | - All the companies and positions exist so keep researching until you find the information for each one.
30 | - Make sure you each researched position for each company contains 3 blog articles and 3 YouTube interviews.
31 | """,
32 | backstory="""As a Company Research Manager, you are responsible for aggregating all the researched information
33 | into a list.""",
34 | llm=self.llm,
35 | tools=[self.searchInternetTool, self.youtubeSearchTool],
36 | verbose=True,
37 | allow_delegation=True
38 | )
39 |
40 | def company_research_agent(self) -> Agent:
41 | return Agent(
42 | role="Company Research Agent",
43 | goal="""Look up the specific positions for a given company and find urls for 3 recent blog articles and
44 | the url and title for 3 recent YouTube interview for each person in the specified positions. It is your job to return this collected
45 | information in a JSON object""",
46 | backstory="""As a Company Research Agent, you are responsible for looking up specific positions
47 | within a company and gathering relevant information.
48 |
49 | Important:
50 | - Once you've found the information, immediately stop searching for additional information.
51 | - Only return the requested information. NOTHING ELSE!
52 | - Make sure you find the persons name who holds the position.
53 | - Do not generate fake information. Only return the information you find. Nothing else!
54 | """,
55 | tools=[self.searchInternetTool, self.youtubeSearchTool],
56 | llm=self.llm,
57 | verbose=True
58 | )
59 |
--------------------------------------------------------------------------------
/nextjs_app/hooks/useCrewJob.tsx:
--------------------------------------------------------------------------------
1 | "use client";
2 |
3 | import axios from "axios";
4 | import { useEffect, useState } from "react";
5 | import toast from "react-hot-toast";
6 |
7 | export type EventType = {
8 | data: string;
9 | timestamp: string;
10 | };
11 |
12 | export type NamedUrl = {
13 | name: string;
14 | url: string;
15 | };
16 |
17 | export type PositionInfo = {
18 | company: string;
19 | position: string;
20 | name: string;
21 | blog_articles_urls: string[];
22 | youtube_interviews_urls: NamedUrl[];
23 | };
24 |
25 | export const useCrewJob = () => {
26 | // State
27 | const [running, setRunning] = useState(false);
28 | const [companies, setCompanies] = useState([]);
29 | const [positions, setPositions] = useState([]);
30 | const [events, setEvents] = useState([]);
31 | const [positionInfoList, setPositionInfoList] = useState([]);
32 | const [currentJobId, setCurrentJobId] = useState("");
33 |
34 | // useEffects
35 | useEffect(() => {
36 | let intervalId: number;
37 | console.log("currentJobId", currentJobId);
38 |
39 | const fetchJobStatus = async () => {
40 | try {
41 | console.log("calling fetchJobStatus");
42 | const response = await axios.get<{
43 | status: string;
44 | result: { positions: PositionInfo[] };
45 | events: EventType[];
46 | }>(`http://localhost:3001/api/crew/${currentJobId}`);
47 | const { status, events: fetchedEvents, result } = response.data;
48 |
49 | console.log("status update", response.data);
50 |
51 | setEvents(fetchedEvents);
52 | if (result) {
53 | console.log("setting job result", result);
54 | console.log("setting job positions", result.positions);
55 | setPositionInfoList(result.positions || []);
56 | }
57 |
58 | if (status === "COMPLETE" || status === "ERROR") {
59 | if (intervalId) {
60 | clearInterval(intervalId);
61 | }
62 | setRunning(false);
63 | toast.success(`Job ${status.toLowerCase()}.`);
64 | }
65 | } catch (error) {
66 | if (intervalId) {
67 | clearInterval(intervalId);
68 | }
69 | setRunning(false);
70 | toast.error("Failed to get job status.");
71 | console.error(error);
72 | }
73 | };
74 |
75 | if (currentJobId !== "") {
76 | intervalId = setInterval(fetchJobStatus, 1000) as unknown as number;
77 | }
78 |
79 | return () => {
80 | if (intervalId) {
81 | clearInterval(intervalId);
82 | }
83 | };
84 | }, [currentJobId]);
85 |
86 | const startJob = async () => {
87 | // Clear previous job data
88 | setEvents([]);
89 | setPositionInfoList([]);
90 | setRunning(true);
91 |
92 | try {
93 | const response = await axios.post<{ job_id: string }>(
94 | "http://localhost:3001/api/crew",
95 | {
96 | companies,
97 | positions,
98 | }
99 | );
100 |
101 | toast.success("Job started");
102 |
103 | console.log("jobId", response.data.job_id);
104 | setCurrentJobId(response.data.job_id);
105 | } catch (error) {
106 | toast.error("Failed to start job");
107 | console.error(error);
108 | setCurrentJobId("");
109 | }
110 | };
111 |
112 | return {
113 | running,
114 | events,
115 | setEvents,
116 | positionInfoList,
117 | setPositionInfoList,
118 | currentJobId,
119 | setCurrentJobId,
120 | companies,
121 | setCompanies,
122 | positions,
123 | setPositions,
124 | startJob,
125 | };
126 | };
127 |
--------------------------------------------------------------------------------