Skip to content
This repository has been archived by the owner on May 10, 2024. It is now read-only.

Dev #50

Merged
merged 38 commits into from
Nov 25, 2023
Merged

Dev #50

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
27973a1
chatgpt-calling and dev branch
eleonora-sc Nov 9, 2023
f32a98c
parts fo NewProjectModal.js and Graph.js need to get transferred to t…
eleonora-sc Nov 9, 2023
59a4844
Parsing chatGPT result may work in mongoDB/createProject route.js
schromya Nov 9, 2023
0c8fc41
moved parsing intto chat-gpt/route.js
eleonora-sc Nov 9, 2023
6b132cb
Fixed a couple bugs
schromya Nov 9, 2023
db3ffc5
Closer to working but doesn't store full tree
schromya Nov 9, 2023
7b7aa57
Now shows full graph
schromya Nov 9, 2023
e7210b2
Merge branch 'dev' of https://github.com/quayside-app/quayside_mvp in…
schromya Nov 9, 2023
551a198
Fixed parent node issue
schromya Nov 9, 2023
4192570
added signout and icon
AKashton Nov 15, 2023
3538539
fixed linter issues
AKashton Nov 16, 2023
efbe104
linter fixes
AKashton Nov 16, 2023
0254fa3
Merge pull request #46 from quayside-app/ashton-logout-userface
AKashton Nov 16, 2023
18d94d9
Seperated createTask into new route/function
schromya Nov 16, 2023
bbfbd74
Added getUsers function so it can be used server side
schromya Nov 16, 2023
bdeb27e
Took mongoose stuff out of options
schromya Nov 16, 2023
80fcbd9
Fixed linter stuff
schromya Nov 16, 2023
979e3b8
Merge branch 'dev' of https://github.com/quayside-app/quayside_mvp in…
schromya Nov 16, 2023
f74bbce
Added Delete project button (but doesn't do anything yet
schromya Nov 23, 2023
ff46725
Fixed formatting
schromya Nov 23, 2023
2920442
Linted
schromya Nov 23, 2023
1f4fdca
got delete working
schromya Nov 23, 2023
0a2fa42
Merge pull request #48 from quayside-app/chatgpt-calling-2
erwilliams64 Nov 23, 2023
016fdac
Got sidebar refreshing when path updates
schromya Nov 23, 2023
15125a8
Fixed sidebar so now updates when project is deleted
schromya Nov 23, 2023
db6251a
Added documentation
schromya Nov 23, 2023
03c51d0
Model now routes to new project when created.
schromya Nov 23, 2023
a20b425
Added loading functionality when a project is created
schromya Nov 23, 2023
3a866b2
Fixed left sidebar vertical overflow
schromya Nov 23, 2023
f792c79
Added truncation to left sidebard projects so a max of 200 pixls
schromya Nov 23, 2023
7bd21f3
Changed delete to red
schromya Nov 23, 2023
318f4aa
Added querying single project by id to route.js
schromya Nov 23, 2023
1134d30
Fixed sidebar I think
schromya Nov 24, 2023
bb5df54
Got graph back and project name
schromya Nov 24, 2023
6e55e80
Fixed sidebar a bit more
schromya Nov 24, 2023
4a7194c
Fixed formatting
schromya Nov 24, 2023
b8d41a7
Merged with dev
schromya Nov 24, 2023
dd2fae9
Merge pull request #49 from quayside-app/usability
AKashton Nov 24, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions public/svg/load.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 0 additions & 1 deletion public/svg/search.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 1 addition & 3 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@ Welcome to our Quayside MVP. The tech stack for this is the MERN framework (Mong
## Setup
You need to install npm (you can do this by installing [Node.js](https://nodejs.org/en/download)). Once that is done, run `npm install` in this directory to install all the requirements.

For accessing the mongo database locally, you will need the following generated database Atlas creds in an `.env.local` file (fyi, these creds are different than your creds to login to Mongo Atlas). In the same file you will also ned your oath creds(for Google and GitHub as examples). Here is the forma:

For accessing the mongo database locally, you will need the following generated database Atlas creds in an `.env.local` file (fyi, these creds are different than your creds to login to Mongo Atlas). In the same file you will also ned your oath creds(for google and github as examples). You will also need your chatGPT key. Here is the forma:
For accessing the mongo database locally, you will need the following generated database Atlas creds in an `.env.local` file (fyi, these creds are different than your creds to login to Mongo Atlas). In the same file you will also ned your oath creds(for google and github as examples). You will also need your chatGPT key. Here is the format:

```bash
MONGO_USERNAME=<your username>
Expand Down
41 changes: 38 additions & 3 deletions src/app/[...projectIds]/page.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
'use client'
import TreeGraph from '../../components/Graph'
import Button from '../../components/Button'
import { useRouter } from 'next/navigation'
import { useEffect, useState } from 'react'

/**
* Renders a page component that displays the tree graph with a dynamic route
Expand All @@ -10,11 +14,42 @@ import TreeGraph from '../../components/Graph'
* the project IDs and a TreeGraph component for the project.
*/
export default function page ({ params }) {
const [project, setProject] = useState(null)
const router = useRouter()
async function deleteProject (projectID, router) {
await fetch(`/api/mongoDB/deleteProject?projectID=${projectID}`, {
method: 'DELETE'
}).catch(error => console.error('Error:', error))
router.push('/')
}

useEffect(() => {
// Fetch project data
fetch(`/api/mongoDB/getProjects?projectID=${params.projectIds}`, {
method: 'GET'
}).then(async (response) => {
const body = await response.json()
if (!response.ok) {
console.error(body.message)
}
setProject(body.projects)
}).catch(error => {
console.error('Error querying project data:', error)
})
}, [])

return (
<div>
<div className='p-4 text-xl flex w-full flex-wrap'>

<div className='flex w-full'>
<div className='flex w-10/12 flex-wrap'> Project: {project && project.name} </div>
<div className='flex w-2/12 justify-end'>
{/* On click, delete project, return to home page, and refresh */}
<Button label='Delete Project' clickAction={() => { deleteProject(params.projectIds, router) }} className='bg-red-800 ' isCentered='true' />
</div>
</div>

Project: {params.projectIds}
<TreeGraph projectID={params.projectIds} />
<TreeGraph projectID={params.projectIds} className='flex w-full' />
</div>
)
}
21 changes: 6 additions & 15 deletions src/app/api/auth/[...nextauth]/options.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import GitHubProvider from 'next-auth/providers/github'
import GoogleProvider from 'next-auth/providers/google'
import mongoose from 'mongoose'
import { URI } from '../../mongoDB/mongoData.js'
import { User } from '../../mongoDB/mongoModels.js'
import { getUsers } from '../../mongoDB/getUsers/getUsers'
import { createUser } from '../../mongoDB/createUser/createUser'

export const options = {
// configure one or more authentication providers
Expand All @@ -20,8 +19,7 @@ export const options = {
],
callbacks: {
async signIn ({ user, account, profile }) {
if (mongoose.connection.readyState !== 1) await mongoose.connect(URI)
const existingUser = await User.findOne({ email: user.email })
const existingUser = await getUsers(null, user.email)

if (existingUser) {
return true
Expand All @@ -32,13 +30,7 @@ export const options = {
const lastName = nameParts.length > 1 ? nameParts.slice(1).join(' ') : ''

// Create User
const newUser = await User.create({
firstName,
lastName,
email: user.email,
username: '',
teamIDs: []
})
const newUser = await createUser(user.email, firstName, lastName)
return !!newUser // Return true if creation is successful
}
},
Expand All @@ -52,9 +44,8 @@ export const options = {
async jwt ({ token, user, account, profile, isNewUser }) {
// This callback is called whenever a JWT is created. So session.userId is the mongo User _id
if (user) {
if (mongoose.connection.readyState !== 1) await mongoose.connect(URI)
const mongoUser = await User.findOne({ email: user.email }) // TODO maybe store this so don't have to do more queries
token.sub = mongoUser._id
const mongoUsers = await getUsers(null, user.email)
token.sub = mongoUsers[0]._id
}
return token
}
Expand Down
114 changes: 114 additions & 0 deletions src/app/api/chat-gpt/generateTasks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
import OpenAI from 'openai'

/**
* Asynchronously generates a list of tasks and subtasks based on a user prompt using OpenAI's GPT-3.5 model.
* The function connects to OpenAI using an API key and sends a formatted request to receive a structured
* breakdown of tasks and subtasks. It processes the response to create a hierarchical list of tasks.
*
* This function can be used server side.Alternatively the POST request in the route.js should
* be used on client side (which uses this logic).
*
* @param {string} userPrompt - A prompt describing the project or task for which subtasks need to be generated.
* @returns {Promise<Array.<Object>>} - A promise that resolves to an array of task objects, each with a structure
* containing the task's id, name, parent, and an array of subtasks.
* @throws {Error} - Throws an error if the API key is missing, if there's an issue with the OpenAI request, or if
* the response processing encounters an error.
*
* @example
* // Example of using generateTasks function
* generateTasks('Plan a company retreat for 100 employees')
* .then(tasks => console.log('Generated tasks:', tasks))
* .catch(error => console.error('Error in generating tasks:', error));
*
* // Expected output structure of each task object:
* // {
* // id: '1',
* // name: 'Task name',
* // parent: 'root',
* // subtasks: [{ id: '1.1', name: 'Subtask name', parent: '1' }, ...]
* // }
*/
export async function generateTasks (userPrompt) {
const userAPIKey = process.env.QUAYSIDE_API_KEY
if (!userAPIKey) {
throw new Error('API key is required')
}

const openai = new OpenAI({
apiKey: userAPIKey
})

const response = await openai.chat.completions.create({
model: 'gpt-3.5-turbo',
messages: [
{
// set the tone of the response you get back
role: 'system',
content: 'You are given as input a project or task that a single person or a team wants to take on.' +
'Divide the task into less than 5 subtasks and list them hierarchically in the format where task 1 has subtasks 1.1, 1.2,...' +
'and task 2 has subtasks 2.1, 2.2, 2.3,... and so forth' +
'Make sure that every task is on one line after the number, NEVER create new paragraphs within a task or subtask.'
},
{
// here is where the user prompt gets used
role: 'user',
content: userPrompt
}
],
temperature: 0,
max_tokens: 1024,
top_p: 1,
frequency_penalty: 0,
presence_penalty: 0
})

const responseString = response.choices[0].message.content
console.log(responseString)
const newTasks = []

// Split the input string into an array of lines. ATTENTION! THIS REQUIRES THE CHATGPT RESPONSE TO PRINT ONE LINE PER TASK/SUBTASK
const lines = responseString.split('\n')

let currentTaskId = null
// Loop through the lines and extract tasks
for (let i = 0; i < lines.length; i++) {
// for every line,
const line = lines[i]
const primaryMatch = line.match(/^(\d+)\.\s(.+)/) // this checks for this structure: 1. <tasktext>
const subtaskMatch = line.match(/^\s+(\d+\.\d+\.?)\s(.+)/) // this checks for this structure: 1.2 <subtasktext> or 1.2. <subtasktext>

if (primaryMatch) {
const taskNumber = primaryMatch[1]
const taskText = primaryMatch[2]
currentTaskId = taskNumber

// making the 1st layer deep of tasks into a dictionary
newTasks.push({
id: taskNumber,
name: taskText,
parent: 'root', // this should be replaced by the user prompt
subtasks: []

})
console.log('Parsed task', taskNumber, ':', taskText)
} else if (subtaskMatch) {
const subtaskNumber = subtaskMatch[1]// .replace('.', '_'); // Convert 1.1 to 1_1
const subtaskText = subtaskMatch[2]

// Find the parent task
const parentTask = newTasks.find(task => task.id === currentTaskId)

// If the parent task is found, push the subtask into its subtasks array
if (parentTask) {
parentTask.subtasks.push({
id: subtaskNumber,
name: subtaskText,
parent: currentTaskId
})
console.log('Parsed subtask', subtaskNumber, ':', subtaskText)
}
}
}
console.log('Full dictionary:', newTasks)
return newTasks
}
92 changes: 50 additions & 42 deletions src/app/api/chat-gpt/route.js
Original file line number Diff line number Diff line change
@@ -1,51 +1,59 @@
import { NextResponse } from 'next/server'
import { options } from '../auth/[...nextauth]/options'
import { getServerSession } from 'next-auth/next'
import { generateTasks } from './generateTasks'

import OpenAI from 'openai'

/**
* Handles a POST request to generate tasks based on a user prompt using the ChatGPT model. This endpoint first
* authenticates the user session. If authenticated, it extracts the user prompt from the request and calls
* the `generateTasks` function to generate a structured list of tasks and subtasks. It returns these tasks
* in the response.
*
* @param {Object} request - The incoming request object containing the user prompt in JSON format.
* @returns {Object} - A response object with a status code and either the generated tasks or an error message.
*
* @throws {Error} - Throws an error if there is an issue with authentication, generating tasks, or any other
* internal errors.
*
* @example
* // Example of a POST request to this endpoint
* fetch(`/api/generateTasks`, {
* method: 'POST',
* headers: { 'Content-Type': 'application/json' },
* body: JSON.stringify({ prompt: 'Organize a team-building event' }),
* }).then(async (response) => {
* const body = await response.json();
* if (!response.ok) {
* console.error('Error:', body.message);
* } else {
* console.log('Generated tasks:', body.newTasks);
* }
* }).catch(error => console.error('Error in POST request:', error));
*
* // Expected output structure of each task object:
* // {
* // id: '1',
* // name: 'Task name',
* // parent: 'root',
* // subtasks: [{ id: '1.1', name: 'Subtask name', parent: '1' }, ...]
* // }
*
* @property {string} request.body.prompt - The user prompt used to generate tasks.
*/
export async function POST (request) {
// Authenticates user
const session = await getServerSession(options)
if (!session) {
return NextResponse.json({ success: false, message: 'authentication failed' }, { status: 401 })
}
console.log(process.env.QUAYSIDE_API_KEY) // remove later

// magically getting the user form data from NewProjectModal form
const params = await request.json()
const userAPIKey = params.apiKey
const userPrompt = params.prompt
try {
const session = await getServerSession(options)
if (!session) {
return NextResponse.json({ success: false, message: 'authentication failed' }, { status: 401 })
}

if (!userAPIKey) {
return NextResponse.json({ message: 'API key is required' }, { status: 400 })
}

const openai = new OpenAI({
apiKey: userAPIKey
})
const params = await request.json()
const userPrompt = params.prompt

const response = await openai.chat.completions.create({
model: 'gpt-3.5-turbo',
messages: [
{
// set the tone of the response you get back
role: 'system',
content: 'Given the user prompt, list its subtasks and subtask\'s subtasks, etc... in a tree structure starting from 1, and going deeper with 1.1, 1.1.1, 1.2, 1.2.2, etc.' +
'Ensure every task and subtask has 5 or less children tasks. Keep the subtasks directly related to the user input task.'
},
{
// here is where the user prompt gets used
role: 'user',
content: userPrompt
}
],
temperature: 0,
max_tokens: 1024,
top_p: 1,
frequency_penalty: 0,
presence_penalty: 0
})
const newTasks = generateTasks(userPrompt)

return NextResponse.json(response)
return NextResponse.json({ newTasks }, { status: 500 })
} catch (error) {
return NextResponse.json({ message: 'Error calling ChatGPT:' + error }, { status: 500 })
}
}
Loading