Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
b0bb2e8
Route with comments
TyHil Nov 12, 2025
67fb79f
RMP fetch + Gemini processing
davis118 Nov 13, 2025
d8aafec
fix unrelated lint warnings. how'd they end up here?
davis118 Nov 13, 2025
740e647
prettier
davis118 Nov 13, 2025
b0b6dfd
cache by prof name (cost)
davis118 Nov 13, 2025
852d9c3
remove useCache; add next.js cache; simplify SearchQuery
davis118 Nov 23, 2025
39718b4
Tweak prompt and switch to Gemini 2.5 Flash Lite
TyHil Nov 23, 2025
56632d6
Revert "fix unrelated lint warnings. how'd they end up here?"
davis118 Nov 23, 2025
4db3672
Placeholder for AI generated Syllabus Summary Feature 550
barkat-10 Dec 1, 2025
8b242b3
Feature 550: used props rather than static placeholders
barkat-10 Dec 1, 2025
43a8728
Merge branch 'develop' into rmp-summary
TyHil Dec 14, 2025
f86e854
Merge branch 'rmp-summary' of https://github.com/UTDNebula/utd-trends…
TyHil Dec 14, 2025
df46223
Fix curly brackets for search results
TyHil Dec 14, 2025
3125e04
Fix extra data in cached results
TyHil Dec 14, 2025
c1d9738
Tweak prompt
TyHil Dec 14, 2025
06caee5
Frontend UI
TyHil Dec 14, 2025
ca84b55
Force refetch summary when search query changes
TyHil Dec 16, 2025
3706b9a
Remove next cache validation
shurgbee Dec 21, 2025
9f1f6aa
Added Summary Tooltip and removed media data headers
shurgbee Dec 23, 2025
15b3eec
Merge branch 'develop' into rmp-summary
shurgbee Jan 1, 2026
0eecd70
run format
shurgbee Jan 1, 2026
576c2fb
fix import in SingleProfInput
egsch Jan 3, 2026
665563c
copy over rmpSummary route for syllabusSummary
AbhiramTadepalli Jan 8, 2026
d8037af
use syllabus_uri param
AbhiramTadepalli Jan 8, 2026
22bb4df
build syllabus gemini pipeline
AbhiramTadepalli Jan 9, 2026
1bc5da5
use 2.5-flash-lite model for now
AbhiramTadepalli Jan 9, 2026
1d8b383
try to make prompt better
AbhiramTadepalli Jan 9, 2026
7d3f04c
Merge branch 'develop' into Feature550
AbhiramTadepalli Jan 9, 2026
10391cd
Merge branch 'syllabus-summary' into Feature550
AbhiramTadepalli Jan 9, 2026
3e8fedf
format
AbhiramTadepalli Jan 9, 2026
5084b04
Create SyllabusSummary component and link the api to the frontend
AbhiramTadepalli Jan 10, 2026
d23b1c4
Prompt changed. Grade scale not available message added.
barkat-10 Jan 10, 2026
ea250a4
just remove non-existant fields & lint
AbhiramTadepalli Jan 11, 2026
099dd09
loading state ui should be inside the view syllabus summary
AbhiramTadepalli Jan 11, 2026
1cb4313
Prompt specifications changed.
barkat-10 Jan 11, 2026
bc2e5fe
Different versions of Prompts for testing and opinions
barkat-10 Jan 16, 2026
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
416 changes: 399 additions & 17 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"dependencies": {
"@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0",
"@google/genai": "^1.29.1",
"@ianvs/prettier-plugin-sort-imports": "^4.7.0",
"@mui/icons-material": "^7.0.2",
"@mui/material": "^7.0.2",
Expand Down
8 changes: 2 additions & 6 deletions src/app/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,19 +62,15 @@ export default function Home(props: Props) {
rel="noreferrer"
className="bg-royal dark:bg-cornflower-300 text-cornflower-50 dark:text-haiti py-3 px-5 rounded transition hover:scale-[1.01] text-center flex gap-2 items-center mr-auto"
>
<Image
<img
className="h-8 -my-1 -ml-2 hidden dark:block"
src="/icon-black.svg"
alt=""
width={32}
height={32}
/>
<Image
<img
className="h-8 -my-1 -ml-2 block dark:hidden"
src="/icon-white.svg"
alt=""
width={32}
height={32}
/>
<span>
<b>Spring 2026</b> courses are now on Trends!
Expand Down
144 changes: 144 additions & 0 deletions src/app/api/rmpSummary/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import fetchRmp from '@/modules/fetchRmp';
import type { SearchQuery } from '@/types/SearchQuery';
import { GoogleGenAI } from '@google/genai';
import { NextResponse } from 'next/server';

export async function GET(request: Request) {
const API_URL = process.env.NEBULA_API_URL;
if (typeof API_URL !== 'string') {
return NextResponse.json(
{ message: 'error', data: 'API URL is undefined' },
{ status: 500 },
);
}
const API_KEY = process.env.NEBULA_API_KEY;
if (typeof API_KEY !== 'string') {
return NextResponse.json(
{ message: 'error', data: 'API key is undefined' },
{ status: 500 },
);
}
const API_STORAGE_BUCKET = process.env.NEBULA_API_STORAGE_BUCKET;
if (typeof API_STORAGE_BUCKET !== 'string') {
return NextResponse.json(
{ message: 'error', data: 'API storage bucket is undefined' },
{ status: 500 },
);
}
const API_STORAGE_KEY = process.env.NEBULA_API_STORAGE_KEY;
if (typeof API_STORAGE_KEY !== 'string') {
return NextResponse.json(
{ message: 'error', data: 'API storage key is undefined' },
{ status: 500 },
);
}

const { searchParams } = new URL(request.url);
const profFirst = searchParams.get('profFirst');
const profLast = searchParams.get('profLast');
if (typeof profFirst !== 'string' || typeof profLast !== 'string') {
return NextResponse.json(
{ message: 'error', data: 'Incorrect query parameters' },
{ status: 400 },
);
}

// Check cache
const filename = profFirst + profLast + '.txt';
const url = API_URL + 'storage/' + API_STORAGE_BUCKET + '/' + filename;
const headers = {
'x-api-key': API_KEY,
'x-storage-key': API_STORAGE_KEY,
};
const cache = await fetch(url, { headers });
if (cache.ok) {
const cacheData = await cache.json();
// Cache is valid for 30 days
if (
new Date(cacheData.data.updated) >
new Date(Date.now() - 1000 * 60 * 60 * 24 * 30)
) {
const mediaData = await fetch(cacheData.data.media_link);
if (mediaData.ok) {
return NextResponse.json(
{ message: 'success', data: await mediaData.text() },
{ status: 200 },
);
}
}
}

// Fetch RMP
const searchQuery: SearchQuery = {
profFirst: profFirst,
profLast: profLast,
};
const rmp = await fetchRmp(searchQuery, true);

if (!rmp?.ratings) {
return NextResponse.json(
{ message: 'error', data: 'No ratings found' },
{ status: 500 },
);
}
if (rmp.ratings.edges.length < 5) {
return NextResponse.json(
{ message: 'error', data: 'Not enough ratings for a summary' },
{ status: 500 },
);
}

// AI
const prompt = `Summarize the Rate My Professors reviews of professor ${profFirst} ${profLast}:

${rmp.ratings.edges.map((rating) => rating.node.comment.replaceAll('\n', ' ').slice(0, 500)).join('\n')}

Summary requirements:
- Summarize the reviews in a concise and informative manner.
- Focus on the structure of the class, exams, projects, homeworks, and assignments.
- Be respectful but honest, like a student writing to a peer.
- Respond in plain-text (no markdown), in 30 words.
`;
const GEMINI_SERVICE_ACCOUNT = process.env.GEMINI_SERVICE_ACCOUNT;
if (typeof GEMINI_SERVICE_ACCOUNT !== 'string') {
return NextResponse.json(
{ message: 'error', data: 'GEMINI_SERVICE_ACCOUNT is undefined' },
{ status: 500 },
);
}
const serviceAccount = JSON.parse(GEMINI_SERVICE_ACCOUNT);
const geminiClient = new GoogleGenAI({
vertexai: true,
project: serviceAccount.project_id,
googleAuthOptions: {
credentials: {
client_email: serviceAccount.client_email,
private_key: serviceAccount.private_key,
},
},
});
const response = await geminiClient.models.generateContent({
model: 'gemini-2.5-flash-lite',
contents: prompt,
});

// Cache response
const cacheResponse = await fetch(url, {
method: 'POST',
headers: headers,
body: response.text,
});

if (!cacheResponse.ok) {
return NextResponse.json(
{ message: 'error', data: 'Failed to cache response' },
{ status: 500 },
);
}

// Return
return NextResponse.json(
{ message: 'success', data: response.text },
{ status: 200 },
);
}
Loading
Loading