Skip to content
Merged
30 changes: 25 additions & 5 deletions course-matrix/backend/src/constants/availableFunctions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ export const availableFunctions: AvailableFunctions = {
try {
// Retrieve user_id
const user_id = (req as any).user.id;

// Retrieve user timetable item based on user_id
let timeTableQuery = supabase
.schema("timetable")
Expand All @@ -63,7 +62,11 @@ export const availableFunctions: AvailableFunctions = {
};
}

return { status: 200, data: timetableData };
return {
status: 200,
timetableCount: timetableData.length,
data: timetableData,
};
} catch (error) {
console.log(error);
return { status: 400, error: error };
Expand Down Expand Up @@ -198,13 +201,33 @@ export const availableFunctions: AvailableFunctions = {
try {
// Extract event details and course information from the request
const { name, semester, courses, restrictions } = args;
// Get user id from session authentication to insert in the user_id col
const user_id = (req as any).user.id;

if (name.length > 50) {
return {
status: 400,
error: "timetable title is over 50 characters long",
};
}

// Timetables cannot exceed the size of 25.
const { count: timetable_count, error: timetableCountError } =
await supabase
.schema("timetable")
.from("timetables")
.select("*", { count: "exact", head: true })
.eq("user_id", user_id);

console.log(timetable_count);

if ((timetable_count ?? 0) >= 25) {
return {
status: 400,
error: "You have exceeded the limit of 25 timetables",
};
}

const courseOfferingsList: OfferingList[] = [];
const validCourseOfferingsList: GroupedOfferingList[] = [];
const maxdays = getMaxDays(restrictions);
Expand Down Expand Up @@ -277,9 +300,6 @@ export const availableFunctions: AvailableFunctions = {

// ------ CREATE FLOW ------

// Get user id from session authentication to insert in the user_id col
const user_id = (req as any).user.id;

// Retrieve timetable title
const schedule = trim(validSchedules)[0];
if (!name || !semester) {
Expand Down
13 changes: 8 additions & 5 deletions course-matrix/backend/src/controllers/aiController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ export const chat = asyncHandler(async (req: Request, res: Response) => {
- Allowing natural language queries about courses, offerings, and academic programs
- Providing personalized recommendations based on degree requirements and course availability
- Creating, reading, updating, and deleting user timetables based on natural language

## Your Capabilities
- Create new timetables based on provided courses and restrictions
- Update timetable names and semesters
Expand All @@ -266,11 +266,12 @@ export const chat = asyncHandler(async (req: Request, res: Response) => {
}/dashboard/timetable?edit=[[TIMETABLE_ID]] , where TIMETABLE_ID is the id of the respective timetable.
- If the user provides a course code of length 6 like CSCA08, then assume they mean CSCA08H3 (H3 appended)
- If the user wants to create a timetable:
1. First call getCourses to get course information on the requested courses,
2. If the user provided a semester, then call getOfferings with the provided courses and semester to ensure the courses are actually offered in the semester.
1. First call getTimetables to refetch most recent info on timetable names + timetableCount. DO NOT assume timetable names and # of timetables is same since last query.
2. Then call getCourses to get course information on the requested courses,
3. If the user provided a semester, then call getOfferings with the provided courses and semester to ensure the courses are actually offered in the semester.
a) If a course is NOT returned by getOFferings, then list it under "Excluded courses" with "reason: not offered in [provided semester]"
b) If no courses have offerings, then do not generate the timetable.
3. Lastly, call generateTimetable with the provided information.
4. Lastly, call generateTimetable with the provided information.
- Do not make up fake courses or offerings.
- If a user asks about a course that you do not know of, acknowledge this.
- You can only edit title of the timetable, nothing else. If a user tries to edit something else, acknowledge this limitation.
Expand All @@ -280,12 +281,14 @@ export const chat = asyncHandler(async (req: Request, res: Response) => {
- After a deletion has been cancelled, /timetable confirm will do nothing. If the user wants to delete again after cancelling, they must specify so.
- Do not create multiple timetables for a single user query. Each user query can create at most 1 timetable
- If you try to update or create a timetable but you get an error saying a timetable with the same name already exists, then ask the user to rename
- It is possible for users to delete timetables / update them manually. For this reason, always refetch getTimtables before creation, to get the latest names and timetable count. Do not assume the timetables are the same since the last query.
- If a user asks for the timetable count, always refetch getTimetables. Assume this count could have changed between user queries.
`,
messages,
tools: {
getTimetables: tool({
description:
"Get all the timetables of the currently logged in user.",
"Get all the timetables of the currently logged in user AND the number of timetables of the currently logged in user",
parameters: z.object({}),
execute: async (args) => {
return await availableFunctions.getTimetables(args, req);
Expand Down
24 changes: 17 additions & 7 deletions course-matrix/backend/src/controllers/timetablesController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,21 @@ export default {
.status(400)
.json({ error: "Timetable Title cannot be over 50 characters long" });
}
// Timetables cannot exceed the size of 25.
const { count: timetable_count, error: timetableCountError } =
await supabase
.schema("timetable")
.from("timetables")
.select("*", { count: "exact", head: true })
.eq("user_id", user_id);

console.log(timetable_count);

if ((timetable_count ?? 0) >= 25) {
return res
.status(400)
.json({ error: "You have exceeded the limit of 25 timetables" });
}

// Check if a timetable with the same title already exist for this user
const { data: existingTimetable, error: existingTimetableError } =
Expand All @@ -52,6 +67,7 @@ export default {
.json({ error: "A timetable with this title already exists" });
}
//Create query to insert the user_id and timetable_title into the db

let insertTimetable = supabase
.schema("timetable")
.from("timetables")
Expand Down Expand Up @@ -184,7 +200,6 @@ export default {

//Retrieve the authenticated user
const user_id = (req as any).user.id;

//Retrieve users allowed to access the timetable
const { data: timetableUserData, error: timetableUserError } =
await supabase
Expand All @@ -194,15 +209,13 @@ export default {
.eq("user_id", user_id)
.eq("id", id)
.maybeSingle();

if (timetableUserError)
return res.status(400).json({ error: timetableUserError.message });

//Validate timetable validity:
if (!timetableUserData || timetableUserData.length === 0) {
return res.status(404).json({ error: "Calendar id not found" });
}

// Check if another timetable with the same title already exist for this user
const { data: existingTimetable, error: existingTimetableError } =
await supabase
Expand All @@ -213,7 +226,6 @@ export default {
.eq("timetable_title", timetable_title)
.neq("id", id)
.maybeSingle();

if (existingTimetableError) {
return res.status(400).json({ error: existingTimetableError.message });
}
Expand All @@ -223,7 +235,6 @@ export default {
.status(400)
.json({ error: "Another timetable with this title already exists" });
}

let updateData: any = {};
if (timetable_title) updateData.timetable_title = timetable_title;
if (semester) updateData.semester = semester;
Expand All @@ -240,13 +251,11 @@ export default {
.eq("id", id)
.select()
.single();

const { data: timetableData, error: timetableError } =
await updateTimetableQuery;

if (timetableError)
return res.status(400).json({ error: timetableError.message });

// If no records were updated due to non-existence timetable or it doesn't belong to the user.
if (!timetableData || timetableData.length === 0) {
return res.status(404).json({
Expand All @@ -255,6 +264,7 @@ export default {
}
return res.status(200).json(timetableData);
} catch (error) {
console.error(error);
return res.status(500).send({ error });
}
}),
Expand Down
18 changes: 17 additions & 1 deletion course-matrix/frontend/src/pages/Home/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import TimetableCard from "./TimetableCard";
import TimetableCreateNewButton from "./TimetableCreateNewButton";
import { useGetTimetablesQuery } from "../../api/timetableApiSlice";
import { TimetableCompareButton } from "./TimetableCompareButton";
import { useState } from "react";
import { useState, useEffect } from "react";
import TimetableErrorDialog from "../TimetableBuilder/TimetableErrorDialog";
import { useGetTimetablesSharedWithMeQuery } from "@/api/sharedApiSlice";
import SharedCalendar from "../TimetableBuilder/SharedCalendar";
Expand Down Expand Up @@ -78,6 +78,11 @@ const Home = () => {
.sort(sortingFunction);

const [errorMessage, setErrorMessage] = useState<string | null>(null);
const [count, setCount] = useState<number>(0);

useEffect(() => {
if (myTimetablesData !== undefined) setCount(myTimetablesData.length);
}, [myTimetablesData]);
const [activeTab, setActiveTab] = useState("Mine");

const [selectedSharedTimetable, setSelectedSharedTimetable] =
Expand Down Expand Up @@ -123,6 +128,17 @@ const Home = () => {
<div className="mb-4 flex items-center gap-2 relative group">
<h1 className="text-2xl font-medium tracking-tight">My Timetables</h1>
<Pin size={24} className="text-blue-500" />

<h1
className={`${
count >= 25
? "text-sm font-bold text-red-500"
: "text-sm font-normal text-black"
}`}
>
{" "}
(No. Timetables: {count}/25)
</h1>
</div>
<TimetableErrorDialog
errorMessage={errorMessage}
Expand Down