Skip to content

Commit 7aa0fc6

Browse files
committed
Completed Code Whisperer
1 parent 4c80f97 commit 7aa0fc6

11 files changed

+344
-32
lines changed

.env

+8-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
11
PORT=3001
22
MONGODB_URI=mongodb://localhost:27017/codewhisperer
3-
OPENAI_API_KEY={your_openai_api_key}
3+
OPENAI_API_KEY={your_openai_api_key}
4+
BASE_URL=http://localhost:3001
5+
EMAIL_HOST=smtp.sendgrid.net
6+
EMAIL_PORT=587
7+
EMAIL_FROM={your_validated_sending_identity}
8+
EMAIL_USER={username}
9+
EMAIL_PASSWORD={password}
10+
EMAIL_SECURE=false

README.md

+15-14
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,34 @@
11
# CodeWhisperer
22

3-
CodeWhisperer is a Node.js application leveraging Express and MongoDB, with a Bootstrap-based UI. It facilitates conversation with code repositories by summarizing files and projects using OpenAI's gpt-3.5-turbo-16k and gpt-4-turbo-preview models.
3+
CodeWhisperer is an innovative Node.js application that allows users to have conversations with their code repositories. By leveraging the power of OpenAI's AI models, it can summarize text files and entire code repositories to facilitate a chat-like interaction.
44

55
## Overview
66

7-
The app utilizes Express.js for server-side operations and MongoDB as a data storage solution. It uses the Bootstrap framework to create a responsive UI. The application operates by accepting GitHub repository URLs and email addresses, cloning repositories, extracting and summarizing textual content, and storing summaries in MongoDB. Email notifications are sent through Sendgrid upon task completion.
7+
The application is built using Express for server management, MongoDB for data persistence, and Bootstrap for the front-end interface. It primarily interacts with OpenAI's API to create summaries of repositories and engage users in conversations about their code. The app processes GitHub repositories by cloning them, generating summaries through the gpt-3.5-turbo-16k and gpt-4-turbo-preview AI models, and then communicates the results via email using Sendgrid.
88

99
## Features
1010

11-
- Accept GitHub repository URLs and emails on the main page
12-
- Clone, summarize, and delete repositories upon processing
13-
- Interact with OpenAI's API to generate code and project summaries
14-
- Email users with links to interact with their repository summaries
15-
- Display project summary and offer a chat-like interface for Q&A powered by OpenAI
11+
- Accepts GitHub repository URLs and user email addresses for processing
12+
- Clones repositories and summarizes text files and overall project contents
13+
- Communicates with OpenAI's API to generate summaries and answer user queries
14+
- Sends email notifications with links to interact with the repository summary
15+
- Provides a chat interface for dynamic interaction with the project summary
1616

1717
## Getting started
1818

1919
### Requirements
2020

21-
- Node.js
22-
- MongoDB
23-
- An OpenAI API key
21+
- Node.js environment
22+
- MongoDB instance
23+
- OpenAI API key
24+
- Sendgrid credentials for email notifications
2425

2526
### Quickstart
2627

27-
1. Clone the repository to your local machine.
28-
2. Install dependencies with `npm install`.
29-
3. Set up your `.env` file with the necessary environment variables (PORT, MONGODB_URI, and OPENAI_API_KEY).
30-
4. Run the server using `npm start` or `node server.js`.
28+
1. Clone the repository to your machine.
29+
2. Install necessary Node.js packages with `npm install`.
30+
3. Configure the required environment variables within an `.env` file.
31+
4. Run the application using `npm start` or `node server.js`.
3132

3233
### License
3334

chatService.js

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
const OpenAI = require('openai');
2+
const Repository = require('./repository');
3+
require('dotenv').config();
4+
5+
const openai = new OpenAI(process.env.OPENAI_API_KEY);
6+
7+
async function interactWithRepository(uuid, userMessage) {
8+
try {
9+
const repository = await Repository.findOne({ uuid: uuid }).exec();
10+
if (!repository || !repository.isProcessed) {
11+
throw new Error('Repository not found or not processed yet.');
12+
}
13+
14+
// gpt_pilot_debugging_log
15+
console.log('Repository file summaries:', repository.fileSummaries);
16+
17+
const systemMessage = {
18+
role: 'system',
19+
content: `This is a summary of the project: ${repository.summary}`
20+
};
21+
const userMessageObj = {
22+
role: 'user',
23+
content: userMessage
24+
};
25+
26+
const response = await openai.chat.completions.create({
27+
model: 'gpt-4-turbo-preview',
28+
messages: [systemMessage, ...(repository.fileSummaries || []).map(summary => ({ role: 'system', content: summary })), userMessageObj],
29+
max_tokens: 1024,
30+
temperature: 0.5
31+
});
32+
33+
if (!response.choices || response.choices.length === 0 || !response.choices[0].message) {
34+
throw new Error('Invalid response from OpenAI');
35+
}
36+
37+
return response.choices[0].message.content.trim();
38+
} catch (error) {
39+
// gpt_pilot_debugging_log
40+
console.error('Error in interactWithRepository:', error.message, error.stack);
41+
throw error;
42+
}
43+
}
44+
45+
module.exports = {
46+
interactWithRepository
47+
};

emailService.js

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
const nodemailer = require('nodemailer');
2+
require('dotenv').config();
3+
4+
const transporter = nodemailer.createTransport({
5+
host: process.env.EMAIL_HOST, // Use the SMTP server host from .env
6+
port: parseInt(process.env.EMAIL_PORT, 10), // Use the SMTP server port from .env
7+
secure: process.env.EMAIL_SECURE === 'true', // Convert EMAIL_SECURE string to boolean
8+
auth: {
9+
user: process.env.EMAIL_USER, // Authentication email address from .env
10+
pass: process.env.EMAIL_PASSWORD // Email password from .env
11+
},
12+
tls: {
13+
rejectUnauthorized: false
14+
}
15+
});
16+
17+
async function sendEmailNotification(email, uuid, repoUrl) {
18+
console.log(`Preparing to send email notification to ${email}`); // gpt_pilot_debugging_log
19+
20+
const mailOptions = {
21+
from: process.env.EMAIL_FROM, // Sender address from .env
22+
to: email,
23+
subject: 'Your repo is ready for a chat!',
24+
html: `The repository <a href="${repoUrl}">${repoUrl}</a> has been analyzed. You can chat with it at <a href="${process.env.BASE_URL}/explain/${uuid}">${process.env.BASE_URL}/explain/${uuid}</a>.`
25+
};
26+
27+
console.log(`Sending email to ${email} with subject: "${mailOptions.subject}"`); // gpt_pilot_debugging_log
28+
29+
try {
30+
const result = await transporter.sendMail(mailOptions);
31+
console.log(`Email sent to ${email} with response: ${JSON.stringify(result)}`); // gpt_pilot_debugging_log
32+
} catch (error) {
33+
console.error(`Failed to send email notification: ${error}`, error.stack); // gpt_pilot_debugging_log
34+
throw error;
35+
}
36+
}
37+
38+
module.exports = {
39+
sendEmailNotification
40+
};

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,11 @@
1414
"body-parser": "^1.20.2",
1515
"bootstrap": "^5.3.2",
1616
"dotenv": "^16.4.1",
17+
"ejs": "^3.1.9",
1718
"express": "^4.18.2",
1819
"fs-extra": "^11.2.0",
1920
"mongoose": "^8.1.1",
2021
"nodemailer": "^6.9.9",
21-
"nodemailer-sendgrid-transport": "^0.2.0",
2222
"openai": "^4.26.1",
2323
"simple-git": "^3.22.0",
2424
"tmp": "^0.2.1",

processRepository.js

+26-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const Repository = require('./repository');
22
const { cloneAndProcessRepo } = require('./gitHandler');
33
const { generateSummary } = require('./openaiService');
4+
const { sendEmailNotification } = require('./emailService');
45
const fs = require('fs-extra');
56
const OpenAI = require('openai');
67
require('dotenv').config();
@@ -26,19 +27,22 @@ async function processRepoInBackground(githubUrl, email) {
2627
try {
2728
const { processedFiles, tempDirPath: dirPath } = await cloneAndProcessRepo(githubUrl);
2829
tempDirPath = dirPath;
29-
let allSummaries = [];
30+
let fileSummariesObject = {};
3031

3132
for (const file of processedFiles) {
3233
try {
3334
const content = await fs.readFile(file, 'utf8');
3435
const summary = await generateSummary(content);
35-
allSummaries.push(summary);
36+
fileSummariesObject[file] = summary; // Store summary associated with file name
3637
} catch (fileReadError) {
3738
console.error('Error reading file:', fileReadError.message, fileReadError.stack); // gpt_pilot_debugging_log
3839
}
3940
}
4041

41-
const combinedSummaries = allSummaries.join(' ');
42+
const fileSummariesArray = Object.values(fileSummariesObject); // Convert summaries object to array
43+
console.log('File summaries:', fileSummariesArray); // gpt_pilot_debugging_log
44+
45+
const combinedSummaries = fileSummariesArray.join(' ');
4246
const projectSummaryResponse = await openai.chat.completions.create({
4347
model: 'gpt-4-turbo-preview',
4448
messages: [{ role: "system", content: "Summarize this project based on the individual file summaries." }, { role: "user", content: combinedSummaries }],
@@ -47,12 +51,28 @@ async function processRepoInBackground(githubUrl, email) {
4751
});
4852

4953
const projectSummary = projectSummaryResponse.choices[0].message.content.trim();
50-
await Repository.findOneAndUpdate({ githubUrl, email }, { summary: projectSummary, isProcessed: true }, { new: true });
54+
const updatedRepository = await Repository.findOneAndUpdate(
55+
{ githubUrl, email },
56+
{
57+
summary: projectSummary,
58+
isProcessed: true,
59+
fileSummaries: fileSummariesArray // Save the summaries array to the database
60+
},
61+
{ new: true }
62+
);
63+
console.log(`Repository has been processed and file summaries stored: ${githubUrl}`, fileSummariesArray); // gpt_pilot_debugging_log
64+
console.log(`Ready to send email notification to: ${email} for repository: ${githubUrl}`); // gpt_pilot_debugging_log
65+
66+
try {
67+
await sendEmailNotification(email, updatedRepository.uuid, githubUrl);
68+
console.log(`Email notification sent to: ${email} for repository: ${githubUrl}`); // gpt_pilot_debugging_log
69+
} catch (notificationError) {
70+
console.error(`Error sending email notification to ${email}:`, notificationError.message, notificationError.stack); // gpt_pilot_debugging_log
71+
}
5172

52-
console.log(`Repository has been processed: ${githubUrl}`); // gpt_pilot_debugging_log
5373
} catch (error) {
5474
console.error('Error during repository background processing:', error.message, error.stack); // gpt_pilot_debugging_log
55-
throw error; // This preserves the original behavior of rethrowing the error after logging.
75+
throw error;
5676
} finally {
5777
if (tempDirPath) {
5878
try {

public/chat.css

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
.chat-container {
2+
border: 1px solid #ddd;
3+
padding: 10px;
4+
border-radius: 4px;
5+
max-height: 400px;
6+
overflow-y: auto;
7+
}
8+
9+
.message {
10+
padding: 10px;
11+
margin: 5px;
12+
border-radius: 4px;
13+
position: relative;
14+
}
15+
16+
.user-question {
17+
background-color: #007bff;
18+
color: white;
19+
text-align: left;
20+
max-width: 80%;
21+
word-wrap: break-word;
22+
}
23+
24+
.ai-answer {
25+
background-color: #f1f1f1;
26+
border: 2px solid #007bff;
27+
text-align: left;
28+
max-width: 80%;
29+
word-wrap: break-word;
30+
margin-left: auto; /* Align to the right */
31+
}
32+
33+
/* Style adjustments for a better visual distinction between user and AI messages */
34+
.user-question:after {
35+
content: '';
36+
position: absolute;
37+
top: 0;
38+
right: -10px;
39+
width: 0;
40+
height: 0;
41+
border: 10px solid transparent;
42+
border-left-color: #007bff;
43+
border-right: 0;
44+
border-bottom: 0;
45+
margin-top: 5px;
46+
}
47+
48+
.ai-answer:before {
49+
content: '';
50+
position: absolute;
51+
top: 0;
52+
left: -20px; /* Adjust left position to align the triangle properly */
53+
width: 0;
54+
height: 0;
55+
border: 10px solid transparent;
56+
border-right-color: #f1f1f1;
57+
border-left: 0;
58+
border-bottom: 0;
59+
margin-top: 5px;
60+
}
61+
62+
/* Additional styles to make the chat container visually appealing */
63+
.form-group {
64+
margin-bottom: 10px;
65+
}
66+
67+
/* Scroll to bottom for the latest messages */
68+
.chat-container {
69+
scroll-behavior: smooth;
70+
}

repository.js

+8-4
Original file line numberDiff line numberDiff line change
@@ -6,27 +6,31 @@ const repositorySchema = new mongoose.Schema({
66
uuid: { type: String, default: function genUUID() { return uuid.v4(); }, unique: true },
77
email: { type: String, required: true },
88
summary: { type: String, default: '' },
9+
fileSummaries: [{ type: String }], // Added field for file summaries
910
isProcessed: { type: Boolean, default: false }
1011
}, { timestamps: true });
1112

1213
repositorySchema.post('save', function(error, doc, next) {
1314
if (error.name === 'MongoError' && error.code === 11000) {
15+
console.error('MongoDB duplicate key error:', error.message, error.stack); // gpt_pilot_error_log
1416
next(new Error('There was a duplicate key error'));
1517
} else {
1618
next(error);
1719
}
1820
});
1921

20-
repositorySchema.post('save', function(error, doc, next) {
21-
console.log('Repository saved:', doc);
22+
repositorySchema.post('save', function(doc, next) {
23+
console.log('Repository saved:', doc.uuid); // Adjusted for clarity in log
2224
next();
2325
});
2426

2527
repositorySchema.post('validate', function(error, doc, next) {
26-
console.error('Validation error:', error);
28+
if (error) {
29+
console.error('Validation error:', error.message, error.stack); // gpt_pilot_error_log
30+
}
2731
next(error);
2832
});
2933

3034
const Repository = mongoose.model('Repository', repositorySchema);
3135

32-
module.exports = Repository;
36+
module.exports = Repository;

0 commit comments

Comments
 (0)