Skip to content

Commit

Permalink
create button to call AI functions from admin page and clean up some …
Browse files Browse the repository at this point in the history
…endpoint/function names
  • Loading branch information
leihelen committed Oct 26, 2024
1 parent b833d0d commit 4d950b1
Show file tree
Hide file tree
Showing 3 changed files with 64 additions and 17 deletions.
51 changes: 48 additions & 3 deletions client/src/modules/Admin/Components/Admin.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ export const Admin = () => {
const [resettingProfs, setResettingProfs] = useState<number>(0)
const [addSemester, setAddSemester] = useState('')

Check warning on line 30 in client/src/modules/Admin/Components/Admin.tsx

View workflow job for this annotation

GitHub Actions / build

'setAddSemester' is assigned a value but never used
const [isAdminModalOpen, setIsAdminModalOpen] = useState<boolean>(false)
const [isUpdatingAI, setIsUpdatingAI] = useState(false);
const [updateStatusAI, setUpdateStatusAI] = useState(0);

const { isLoggedIn, token, isAuthenticating } = useAuthMandatoryLogin('admin')
const [loading, setLoading] = useState(true)
Expand All @@ -53,7 +55,7 @@ export const Admin = () => {
// splits the reviews into three categories: approved (visible on the website),
// pending (awaiting approval), and reported (hidden and awaiting approval)
useEffect(() => {
async function loadReviews() {
async function loadReviews() {
const pending = await axios.post('/api/admin/reviews/get/pending', {
token: token,
})
Expand Down Expand Up @@ -107,7 +109,7 @@ export const Admin = () => {
review,
pendingReviews
)
setPendingReviews(updatedUnapprovedReviews)
setPendingReviews(updatedUnapprovedReviews)
} else {
const updatedReportedReviews = removeReviewFromList(
review,
Expand All @@ -123,7 +125,7 @@ export const Admin = () => {

// Call when admin would like to mass-approve all of the currently pending reviews.
async function approveAllReviews(reviews: Review[]) {
const response = await axios.post('/api/admin/reviews/approve/all', {token: token})
const response = await axios.post('/api/admin/reviews/approve/all', { token: token })
if (response.status === 200) {
setPendingReviews([])
} else {
Expand Down Expand Up @@ -235,6 +237,29 @@ export const Admin = () => {
})
}

// 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.
function summarizeReviews() {
console.log('Updating all courses with AI');
if (isUpdatingAI) {
return;
}
setIsUpdatingAI(true);
setUpdateStatusAI(1);

axios.post('/api/ai/update-all-courses')
.then((response) => {
setUpdateStatusAI(2);
})
.catch((error) => {
setUpdateStatusAI(-1);
}).finally(() => {
setIsUpdatingAI(false);
});
}


// handle the first click to the "Initialize Database" button. Show an alert
// and update state to remember the next click will be a double click.
function firstClickHandler() {
Expand Down Expand Up @@ -319,6 +344,14 @@ export const Admin = () => {
>
Reset Professors
</button>
<button
disabled={disableInit}
type="button"
className={styles.adminButtons}
onClick={() => summarizeReviews()}
>
Summarize Reviews
</button>
{renderInitButton(doubleClick)}
</div>
</div>
Expand All @@ -329,6 +362,18 @@ export const Admin = () => {
token={token}
/>

<div hidden={!(updateStatusAI === 1)} className="">
<p>Updating courses with AI. Process may take some time.</p>
</div>

<div hidden={!(updateStatusAI === 2)} className="">
<p>Update complete! Check log for details of successful and failed courses.</p>
</div>

<div hidden={!(updateStatusAI === -1)} className="">
<p>Error updating courses with AI.</p>
</div>

<div hidden={!(loadingSemester === 1)} className="">
<p>
Adding New Semester Data. This process can take up to 15 minutes.
Expand Down
16 changes: 9 additions & 7 deletions server/src/ai/ai.functions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ async function summarize(text: string) {
role: "system", content: `
You are given a collection of course reviews provided where each review is separated by a /. You
will then complete two tasks. First you should generate a 50 word summary of all reviews. Then
should create 5 adjectives describing the lectures, assignments, professor, skills, and resources,
you should create 5 adjectives describing the lectures, assignments, professor, skills, and resources,
along with their connotations (positive, negative, neutral). Please only pick one adjective for each.
Please provide the summary and tags in the following format:
Expand All @@ -57,20 +57,22 @@ async function summarize(text: string) {
{ role: "user", content: text }
],
});

const response = completion.choices[0].message.content;
const summaryMatch = response.match(/Summary: ([\s\S]*?)(?=Tags)/);
const summary = summaryMatch ? summaryMatch[1].trim() : "";
const summary = summaryMatch ? summaryMatch[1].trim() : "Summary not found.";
const tagsMatch = response.match(/Tags:\s*([\s\S]*)/);
const tags = tagsMatch ? tagsMatch[1] : "";
const tagsObject: { [key: string]: [string, string] } = {};

tags.split(',').forEach(item => {
const match = item.match(/(\w+): (.+) \((.+)\)/);
if (match) {
const category = match[1].trim();
const adjective = match[2].trim();
const connotation = match[3].trim();
tagsObject[category] = [adjective, connotation];
if (adjective !== "N/A" && connotation != "N/A") {
tagsObject[category] = [adjective, connotation];
}
} else {
console.error("Unexpected format: ", item);
}
Expand All @@ -83,7 +85,7 @@ async function summarize(text: string) {
}

/**
* updateCoursesWithAI.
* updateCourseWithAI.
*
* Takes in a courseId and uses that ID to get all reviews from a course to then
* generate a summary and 5 tags for those reviews. Then updates the classSummary
Expand All @@ -94,7 +96,7 @@ async function summarize(text: string) {
* @returns true if update was successful and false if something went wrong
*
*/
const updateCoursesWithAI = async (courseId: string) => {
const updateCourseWithAI = async (courseId: string) => {
try {
const courseReviews = await Reviews.find({ class: courseId }).exec();

Expand Down Expand Up @@ -220,4 +222,4 @@ export const getCrossListOR = (course) => {
];
};

export { getCoursesWithMinReviews, getReviewsPerCourse as getReviewsForSummary, summarize, updateCoursesWithAI }
export { getCoursesWithMinReviews, getReviewsPerCourse as getReviewsForSummary, summarize, updateCourseWithAI }
14 changes: 7 additions & 7 deletions server/src/ai/ai.router.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import express from 'express';
import { CourseIdRequestType } from '../course/course.type';
import { getCoursesWithMinReviews, getReviewsForSummary, summarize, updateCoursesWithAI } from './ai.functions';
import { getCoursesWithMinReviews, getReviewsForSummary, summarize, updateCourseWithAI } from './ai.functions';
const aiRouter = express.Router();
aiRouter.use(express.json());

Expand All @@ -23,7 +23,7 @@ aiRouter.post('/update-all-courses', async (req, res) => {

//loop through each courseId and update
for (const courseId of courseIds) {
const success = await updateCoursesWithAI(courseId);
const success = await updateCourseWithAI(courseId);
if (success) {
results.success.push(courseId);
} else {
Expand All @@ -40,15 +40,15 @@ aiRouter.post('/update-all-courses', async (req, res) => {
}
});

/** Reachable at POST /api/ai/update-summaries
/** Reachable at POST /api/ai/update-course-summary
* @body a courseId
* returns a message indicating whether classSumary, classTags, and freshness have
* been updated successfully for the given courseId
*/
aiRouter.post('/update-summaries', async (req, res) => {
aiRouter.post('/update-course-summary', async (req, res) => {
try {
const { courseId }: CourseIdRequestType = req.body;
const success = await updateCoursesWithAI(courseId);
const success = await updateCourseWithAI(courseId);
if (!success) {
return res.status(404).json({
error: `Failed to update course with ID: ${courseId}. No reviews found.`,
Expand Down Expand Up @@ -80,11 +80,11 @@ aiRouter.post('/text/summarize-reviews', async (req, res) => {
}
});

/** Reachable at POST /api/ai/get/text
/** Reachable at POST /api/ai/get-course-review-text
* @body a course ID
* returns a block of text containing all reviews from that course
*/
aiRouter.post('/get/text', async (req, res) => {
aiRouter.post('/get-course-review-text', async (req, res) => {
try {
const { courseId }: CourseIdRequestType = req.body;
const reviews = await getReviewsForSummary({ courseId });
Expand Down

0 comments on commit 4d950b1

Please sign in to comment.