Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adnan skeleton #14

Closed
wants to merge 46 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
44a4a7d
nit
adnan-farid Apr 18, 2024
7c4b5c5
Merge branch 'Joseph-branch' of https://github.com/micklerj/PDF-GPT i…
adnan-farid Apr 18, 2024
d25ef66
add text input, add file upload, refactor backend,
adnan-farid Apr 19, 2024
cdaa87b
copy of adnans branch
micklerj Apr 19, 2024
d5309dd
implement file structure backend, and remove api
adnan-farid Apr 19, 2024
4d11e11
file changes
adnan-farid Apr 20, 2024
a88a46e
Added chat history but still needs improvement
noah-white-8 Apr 20, 2024
1d1b03b
fix file upload
adnan-farid Apr 20, 2024
94b1232
Updated Chat History and message/history scrolling
noah-white-8 Apr 20, 2024
d6856dc
Merge branch 'adnan-skeleton' of https://github.com/micklerj/PDF-GPT …
noah-white-8 Apr 20, 2024
78648db
More spacing for chat history titles
noah-white-8 Apr 20, 2024
d9d50a2
merge from Adnan
micklerj Apr 20, 2024
1bc2541
nit
adnan-farid Apr 20, 2024
a00905e
nit
adnan-farid Apr 20, 2024
8ed896b
Merge branch 'adnan-skeleton' of https://github.com/micklerj/PDF-GPT …
adnan-farid Apr 20, 2024
39255cd
handle createNewChat and openOldChat, ai Controller/routes
micklerj Apr 21, 2024
be68ceb
changed file naming, working vectorizPDF, createNewChat, and handleSu…
micklerj Apr 21, 2024
04142d2
nit
adnan-farid Apr 21, 2024
b26cbd3
Merge branch 'joseph-branch-2' of https://github.com/micklerj/PDF-GPT…
adnan-farid Apr 21, 2024
ffcc9c4
nit
adnan-farid Apr 21, 2024
492e077
Fixes, Controllers, and Login frontend
ncimino1 Apr 21, 2024
1411777
Merge branch 'adnan-skeleton' of https://github.com/micklerj/PDF-GPT …
ncimino1 Apr 22, 2024
27e715a
load old chat
micklerj Apr 22, 2024
6c22d6f
display pdf name on old convos
micklerj Apr 22, 2024
bd689c8
Merge branch 'joseph-branch-2' of https://github.com/micklerj/PDF-GPT…
adnan-farid Apr 22, 2024
ad1277a
started to fix openOldChat
micklerj Apr 22, 2024
52a3cdb
Additional Frontend files
ncimino1 Apr 22, 2024
9601899
importing css to Login and Register
ncimino1 Apr 22, 2024
3a783b4
Logout button and fixed user/AI icons squeezing
noah-white-8 Apr 22, 2024
37b0be6
need to fix openOldChat
micklerj Apr 22, 2024
8d616af
nit, add cursor, app.js changes for oldChat
adnan-farid Apr 22, 2024
46355b2
Merge branch 'adnan-skeleton' of https://github.com/micklerj/PDF-GPT …
adnan-farid Apr 22, 2024
cfd9f02
Merge branch 'joseph-branch-2' of https://github.com/micklerj/PDF-GPT…
adnan-farid Apr 22, 2024
8dab634
fix switching between conversations
adnan-farid Apr 22, 2024
4b75d3c
Merge branch 'nicholas-branch' of https://github.com/micklerj/PDF-GPT…
adnan-farid Apr 22, 2024
4e38b89
add routing for multipage functionality
adnan-farid Apr 23, 2024
c97b175
fix nav error
adnan-farid Apr 23, 2024
85b06b9
nav fix
adnan-farid Apr 23, 2024
f7dadad
package changes and auth fixes
adnan-farid Apr 23, 2024
eb1cb29
login/logout/register functionality
adnan-farid Apr 24, 2024
e5d930a
getUserID
ncimino1 Apr 24, 2024
3fd862c
CSS for Login and Register
noah-white-8 Apr 24, 2024
18b4376
fix username verification & state and
adnan-farid Apr 24, 2024
2fc4cf1
Resolving pull conflicts
noah-white-8 Apr 24, 2024
de601b2
Merge branch 'adnan-skeleton' of https://github.com/micklerj/PDF-GPT …
noah-white-8 Apr 24, 2024
8ccad68
Fixing CSS after the git pull made it work differently
noah-white-8 Apr 24, 2024
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
3 changes: 2 additions & 1 deletion backend/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
node_modules
.env
PDFs/*
PDFs/*
pdf-uploads/*
74 changes: 56 additions & 18 deletions backend/openAIInterface.js → backend/controllers/aiController.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,14 @@ const {MessagesPlaceholder} = require('@langchain/core/prompts');
const axios = require('axios');

// vectorize the pdf file into a local vector store
const vectorizePDF = async (pdfPath) => {
// param: pdfPath
exports.vectorizePDF = async(req, res) => {
const { pdfPath } = req.body;

if(!pdfPath) {
return res.status(400);
}

const fullPdfPath = `./${pdfPath}.pdf`;
const VECTOR_STORE_PATH = `./${pdfPath}.index`;

Expand All @@ -28,7 +35,6 @@ const vectorizePDF = async (pdfPath) => {
} else {
// If the vector store file doesn't exist, create it
console.log('Creating vector store...');

//const text = fs.readFileSync("murder_mystery_show.txt", "utf8"); // for .txt files
const dataBuffer = fs.readFileSync(fullPdfPath);
const data = await pdf(dataBuffer);
Expand All @@ -54,52 +60,81 @@ const genSessionID = async () => {
}

// generate a new conversation, return the sessionId
const createConvo = async (pdfName) => {
// param: pdfName
// response: convID of new convo
exports.createConvo = async(req, res) => {
const { pdfName } = req.body;

if(!pdfName ) {
return res.status(400);
}

const id = await genSessionID();
const postData = {
"convID": id,
"pdfName": pdfName,
"qaSequence": []
};

axios.post('http://localhost:3500/api/createConversation', postData)
await axios.post('http://localhost:3500/api/createConversation', postData)
.catch(error => {
console.error('Error:', error);
});

return id;
res.status(201).json({ convID: id });
}


const convo = {
exports.convo = {
chatHistory: [],

// upon switching to a new chat, update local chatHistory with chat history from mongoDB
updateHistory: async function(id) {
axios.get('http://localhost:3500/api/getConversation/?convID=' + id)
// params: convID
// response: qaSequence
updateHistory: async (req, res) => {
const { id } = req.body;
if(!id ) {
return res.status(400);
}
console.log("id: ", id);

axios.post('http://localhost:3500/api/getConversation/?convID=' + id)
.then(response => {

// clear local chatHistory
this.chatHistory = [];

exports.convo.chatHistory = [];
// Access the chat history from the response data
const QAs = response.data.qaSequence;
console.log('here');
console.log(QAs);
QAs.forEach(document => {
const question = document.question;
const answer = document.answer;

// add entries to chat history
this.chatHistory.push(new HumanMessage(question));
this.chatHistory.push(new AIMessage(answer));
exports.convo.chatHistory.push(new HumanMessage(question));
exports.convo.chatHistory.push(new AIMessage(answer));
});
// return the qaSequence in the response to display on frontend
res.status(201).json({ qaSequence: QAs });
})
.catch(error => {
console.error('Error:', error);
});
},

// pass in user input to chat bot
askQuestion: async function(id, pdfPath, user_input) {
// params: convID, pdfPath, user_input
// response: AI response
askQuestion: async (req, res) => {
const { id, pdfPath, input } = req.body;
const user_input = `${input}`;

if(!id || !pdfPath || !user_input) {
return res.status(400);
}

const model = new ChatOpenAI({});

// vector store retriever
Expand Down Expand Up @@ -153,8 +188,8 @@ const convo = {
});

// add entries to chat history
this.chatHistory.push(new HumanMessage(user_input));
this.chatHistory.push(new AIMessage(response.answer));
exports.convo.chatHistory.push(new HumanMessage(user_input));
exports.convo.chatHistory.push(new AIMessage(response.answer));

// add entries to database
const putData = {
Expand All @@ -165,13 +200,16 @@ const convo = {
.catch(error => {
console.error('Error:', error);
});

return response.answer;

res.status(201).json({
message: response.answer
});

}

}




module.exports = {vectorizePDF, createConvo, convo};
//module.exports = {vectorizePDF, createConvo, convo};
49 changes: 49 additions & 0 deletions backend/controllers/authController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
const users = require('../model/userlogin');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');

require('dotenv').config();

const handleLogin = async (req, res) => {
const { username, password } = req.body;
if (!username || !password) {
return res.status(400).json({ message: 'Username and Password are required.' });
}
const user = await users.findOne({ username: username }).exec();
if (!user) {
return res.sendStatus(401);
}
const match = await bcrypt.compare(password, user.password);
if (match) {
const convos = []; // Implement later
const accessToken = jwt.sign({ username: user.username }, process.env.ACCESS_TOKEN_SECRET, { expiresIn: '5m' });
const refreshToken = jwt.sign({ username: user.username }, process.env.REFRESH_TOKEN_SECRET, { expiresIn: '1d' });

user.refreshToken = refreshToken;
const result = await user.save();
console.log(result);

res.cookie('jwt', refreshToken, { httpOnly: true, sameSite: 'None', maxAge: 86400000 });
res.json({ convos, accessToken }); // Array of conversation objects, implement later
} else {
res.sendStatus(401);
}
}

const getUserName = async (req, res) => {
const token = req.headers['authorization']?.split(' ')[1]; // Bearer token
if (!token) {
return res.status(401).json({ message: 'No token provided.' });
}

try {
const decoded = jwt.verify(token, process.env.ACCESS_TOKEN_SECRET);
return res.json({ username: decoded.username });
} catch (error) {
return res.status(403).json({ message: 'Invalid token.' });
}
};



module.exports = { handleLogin, getUserName }
9 changes: 3 additions & 6 deletions backend/controllers/conversationController.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const Conversation = require("../model/conversation");

const fetchConversation = async (req, res) => {
exports.fetchConversation = async (req, res) => {
try {
const convID = req.query.convID;
if (!convID) {
Expand All @@ -17,8 +17,7 @@ const fetchConversation = async (req, res) => {
}
};


const newConversation = async(req, res) => {
exports.newConversation = async(req, res) => {
try {
const { convID, pdfName, qaSequence } = req.body;

Expand All @@ -38,7 +37,7 @@ const newConversation = async(req, res) => {
}
}

const addQA = async(req, res) => {
exports.addQA = async(req, res) => {
try {
const { convID } = req.params;
const { question, answer } = req.body;
Expand All @@ -59,5 +58,3 @@ const addQA = async(req, res) => {
console.error(err);
}
}

module.exports = { fetchConversation, newConversation, addQA }
46 changes: 46 additions & 0 deletions backend/controllers/fileController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
const multer = require('multer');
const path = require('path');
const fs = require('fs');

// Helper function to ensure directory exists
const ensureDirSync = (dirPath) => {
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true });
}
}

const storage = multer.diskStorage({
destination: function (req, file, cb) {
console.log('UserID:', req.body.userId);
console.log('ConversationID:', req.body.conversationId);
const userId = req.body.userId;
const conversationId = req.body.conversationId;
if (!userId || !conversationId) {
return cb(new Error("Missing user ID or conversation ID"), false);
}

const uploadPath = path.join('pdf-uploads', userId);
ensureDirSync(uploadPath);
cb(null, uploadPath);
},
filename: function (req, file, cb) {
cb(null, file.originalname);
//cb(null, `${file.fieldname}-${Date.now()}${path.extname(file.originalname)}`);
}
});

const upload = multer({ storage: storage });

exports.uploadFile = upload.single('file');

exports.handleFileUpload = (req, res) => {
if (!req.file) {
return res.status(400).send('No file uploaded.');
}
res.send({
message: 'File uploaded successfully.',
path: req.file.path,
fileName: req.file.filename,
conversationID: req.conversationId
});
};
31 changes: 31 additions & 0 deletions backend/controllers/logoutController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
const users = require('../model/userlogin');
// const jwt = require('jsonwebtoken');
// require('dotenv').config();

const handleLogout = async (req, res) => {
// On client frontend, also delete access token


const cookies = req.cookies;
if (!cookies?.jwt) {
return res.sendStatus(204);
}
const refreshToken = cookies.jwt;

// Is refreshtoken in db?
const user = await users.findOne({ refreshToken }).exec();
if (!user) {
res.clearCookie('jwt', { httpOnly: true, maxAge: 86400000 });
return res.sendStatus(403);
}

// Delete refreshToken in db
user.refreshToken = '';
const result = await user.save();
console.log(result);

res.clearCookie('jwt', { httpOnly: true, maxAge: 86400000 });
res.sendStatus(204);
}

module.exports = { handleLogout }
24 changes: 24 additions & 0 deletions backend/controllers/refreshTokenController
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
const users = require('../model/userlogin');
const jwt = require('jsonwebtoken');

const handleRefreshToken = async (req, res) => {
const cookies = req.cookies;
if (!cookies?.jwt) {
return res.sendStatus(401);
}
const refreshToken = cookies.jwt;

const user = await users.findOne({ refreshToken }).exec();
if (!user) {
return res.sendStatus(403);
}
jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET, (err, decoded) => {
if (err || user.username !== decoded.username) {
return res.sendStatus(403);
}
const accessToken = jwt.sign({ username: user.username }, process.env.ACCESS_TOKEN_SECRET, { expiresIn: '30s' });
res.json({ accessToken });
});
}

module.exports = { handleRefreshToken }
29 changes: 29 additions & 0 deletions backend/controllers/registerController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
const users = require('../model/userlogin');
const bcrypt = require('bcryptjs');
const jwt = require('jsonwebtoken');
require('dotenv').config();

const handleNewUser = async (req, res) => {
const { username, password } = req.body;
if (!username || !password) {
return res.status(400).json({ message: 'Username and Password are required.' });
}
const duplicate = await users.findOne({ username: username }).exec();
if (duplicate) {
return res.sendStatus(409);
}
try {
const hashword = await bcrypt.hash(password, 10);
const result = await users.create({
"username": username,
"password": hashword
});

console.log(result);
res.status(201).json({ message: 'Registration successful' });
} catch (err) {
res.status(500).json({ message: err.message });
}
}

module.exports = { handleNewUser }
Loading
Loading