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

Add summarize reviews button on admin page #471

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

leihelen
Copy link
Contributor

Summary

This PR works on the "Cornellians Say" feature.

PR Type

  • πŸ• Feature
  • πŸ› Bug Fix
  • πŸ“ Documentation Update
  • 🎨 Style
  • πŸ§‘β€πŸ’» Code Refactor
  • πŸ”₯ Performance Improvements
  • βœ… Test
  • πŸ€– Build
  • πŸ” CI
  • πŸ“¦ Chore (Release)
  • ⏩ Revert

Mobile + Desktop Screenshots & Recordings

Screenshot 2024-10-26 at 5 40 13 PM

QA - Test Plan

-tested by pressing the button and checking that the database was correctly updated
-tested that the correct messages were being displayed by running different scenarios

Breaking Changes & Notes

-added a button under admin tools that can be pressed to generate and update database with new summaries/tags
-will show a message while the endpoint is running
-will show a success message if successfully updated and an error message if something went wrong

Added to documentation?

  • πŸ“œ README.md
  • πŸ““ notion
  • πŸ• docstrings

What GIF represents this PR?

gif

@leihelen leihelen requested a review from a team as a code owner October 26, 2024 21:44
Copy link
Contributor

@jacquelinecai jacquelinecai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work Helen! It's amazing that the Cornellians Say feature is coming along smoothly with the backend almost finished! I just left a couple comments to just help maintain consistency in our code and database. I'm so excited to see the future frontend development of this feature!

Comment on lines +32 to +33
const [isUpdatingAI, setIsUpdatingAI] = useState(false);
const [updateStatusAI, setUpdateStatusAI] = useState(0);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once you pull the other two admin button updates from main, you can use the global updating variable and updated variables for the summaries.

Comment on lines +240 to +242
// Call when user selects "Sumarize Reviews" button. Calls endpoint to generate
// summaries and tags using AI for all courses with a freshness above a certain
// threshold, then updates those courses to include these new summaries and tags.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it'd be cleaner if these were docstring comments -- we probably need to update that throughout our codebase too for consistency

Comment on lines +245 to +246
if (isUpdatingAI) {
return;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You shouldn't need to check for this since the new code for disabling buttons should prevent a function call to summarizeReviews() if any button is updating.

setIsUpdatingAI(true);
setUpdateStatusAI(1);

axios.post('/api/ai/update-all-courses')
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe /api/ai/summary/courses would be a better endpoint name?

Also make this an await statement

Comment on lines +373 to +375
<div hidden={!(updateStatusAI === -1)} className="">
<p>Error updating courses with AI.</p>
</div>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like the error message here! I'm wondering if we could do this for all of our admin page buttons by using a singular error variable?

for (const courseId of courseIds) {
const success = await updateCourseWithAI(courseId);
if (success) {
results.success.push(courseId);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To keep this consistent with our current codebase, you could do something like:

const success = await updateCourseWithAI(courseId);
if (success) {
  return res.status(200).json({result: courseId});
}
return res.status(400).json({result: courseId});

And maybe also put the await statement in a try-catch for an error response too (500)

* returns a message indicating whether classSumary, classTags, and freshness have
* been updated successfully for the given courseId
*/
aiRouter.post('/update-course-summary', async (req, res) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Endpoint could be /summary/update instead?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When would we use this endpoint as opposed to the one with update-all? Are there times we may want to target a class to resummarize?

* @body a course ID
* returns a block of text containing all reviews from that course
*/
aiRouter.post('/get-course-review-text', async (req, res) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

review/text or something similar

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When will this endpoint be called?

{ $set: { classSummary: summary, classTags: tags, freshness: 0 } }
);

console.log(`Course ${courseId} updated successfully with summary and tags.`);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of the courseId, use the course subject + number so it's easier to track within Postman.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would definitely be helpful for readability purposes. I think something like

const course = await Classes.findOne({ _id: courseId })
const courseName = course.classSub.toUppercase() + ' ' + course.classNum;

could work, and then put that variable in the print statement

Comment on lines +34 to +40
classSummary: { type: String, required: false }, // 50 word summary of the course generated by openAI
classTags: {
type: Map, // dictionary keys are lectures, assignments, professors, skills, and resources
of: [{ type: String }, { type: String }], // tuple where first element is the adjective and second element is its connotation
required: false
}, // tags describing lectures, assignments, professors, skills, and resources
freshness: { type: Number, default: 0 } // number of reviews since last openAI call
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if it would make sense to put all of this within an AI object instead? Like

AI: 
{
classSummary
classTags
freshness
}

or maybe renaming classTags to classTagsReviews will help differentiate between it and the tags for similar courses

Copy link
Contributor

@qiandrewj qiandrewj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work on the AI feature, and it's super cool that the backend is coming along so well. I left a few questions about where specific endpoints would be used -- it seemed to me that /update-all-courses/ would be the main, if only, route that we use, so not sure if the other ones will be as necessary. Happy to chat about my comments, and amazing job!! πŸŽ†

{ role: "system", content: "You are creating a 50 word summary based on the collection of course reviews provided." },
{
role: "system", content: `
You are given a collection of course reviews provided where each review is separated by a /. You
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe our Chat query could be more descriptive: (ex. ... a collection of college course reviews ...) But if we're happy with the current quality of summaries, it's probably not necessary


const response = completion.choices[0].message.content;
const summaryMatch = response.match(/Summary: ([\s\S]*?)(?=Tags)/);
const summary = summaryMatch ? summaryMatch[1].trim() : "Summary not found.";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we plan to handle in front-end if the summary is not found?

const adjective = match[2].trim();
const connotation = match[3].trim();
if (adjective !== "N/A" && connotation != "N/A") {
tagsObject[category] = [adjective, connotation];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also here, how does the error handling work in the future if there are less than 5 tags or if we have trouble parsing the tags for a certain class?

* returns a message summarizing how many courses were updated successfully
* and how many failed.
*/
aiRouter.post('/update-all-courses', async (req, res) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the route name could be a bit clearer, something like /api/ai/summarize-courses/ maybe?

{ $set: { classSummary: summary, classTags: tags, freshness: 0 } }
);

console.log(`Course ${courseId} updated successfully with summary and tags.`);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would definitely be helpful for readability purposes. I think something like

const course = await Classes.findOne({ _id: courseId })
const courseName = course.classSub.toUppercase() + ' ' + course.classNum;

could work, and then put that variable in the print statement

//get all courses with at least minimum reviews (will be changed later to check for freshness as well)
const courseIds = await getCoursesWithMinReviews(minReviews);
if (!courseIds || courseIds.length === 0) {
return res.status(404).json({ error: `No courses found with at least ${minReviews} reviews.` });
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this not error behavior? So could return a "200 OK" status with the message that no courses that meet both our freshness and minimum review count criteria are currently up for resummarizing

}

//show how many courses were updated successfully
const message = `Update completed. ${results.success.length} courses updated successfully. ${results.failed.length} courses failed to update.`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From above where we took the lack of summary or tags at face value and just filled in with placeholder strings, should we consider those here as "failed" updates?

* returns a message indicating whether classSumary, classTags, and freshness have
* been updated successfully for the given courseId
*/
aiRouter.post('/update-course-summary', async (req, res) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When would we use this endpoint as opposed to the one with update-all? Are there times we may want to target a class to resummarize?

* @body a course ID
* returns a block of text containing all reviews from that course
*/
aiRouter.post('/get-course-review-text', async (req, res) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When will this endpoint be called?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants