|
1 | 1 | import { useEffect, useMemo, useState } from "react"; |
2 | 2 |
|
3 | | -import { useQuery } from "@apollo/client/react"; |
| 3 | +import { useApolloClient, useQuery } from "@apollo/client/react"; |
4 | 4 | import classNames from "classnames"; |
5 | 5 | import { useSearchParams } from "react-router-dom"; |
6 | 6 |
|
7 | 7 | import { ITerm } from "@/lib/api"; |
| 8 | +import { |
| 9 | + GET_CLASSES_BY_IDENTIFIERS, |
| 10 | + ICatalogClass, |
| 11 | +} from "@/lib/api/classes"; |
8 | 12 | import { GetCanonicalCatalogDocument, Semester } from "@/lib/generated/graphql"; |
9 | 13 |
|
10 | 14 | import styles from "./ClassBrowser.module.scss"; |
@@ -102,11 +106,11 @@ export default function ClassBrowser({ |
102 | 106 | useState<EnrollmentFilter | null>(null); |
103 | 107 | const [localOnline, setLocalOnline] = useState<boolean>(false); |
104 | 108 | const [aiSearchActive, setAiSearchActive] = useState<boolean>(false); |
105 | | - const [semanticResults, setSemanticResults] = useState< |
106 | | - Array<{ subject: string; courseNumber: string; score: number }> |
107 | | - >([]); |
| 109 | + const [semanticResults, setSemanticResults] = useState<ICatalogClass[]>([]); |
108 | 110 | const [semanticLoading, setSemanticLoading] = useState(false); |
109 | 111 |
|
| 112 | + const apolloClient = useApolloClient(); |
| 113 | + |
110 | 114 | const { data, loading } = useQuery(GetCanonicalCatalogDocument, { |
111 | 115 | variables: { |
112 | 116 | semester: currentSemester, |
@@ -296,23 +300,10 @@ export default function ClassBrowser({ |
296 | 300 | const index = useMemo(() => getIndex(includedClasses), [includedClasses]); |
297 | 301 |
|
298 | 302 | const filteredClasses = useMemo(() => { |
299 | | - // If AI search is active and we have semantic results, filter by those |
| 303 | + // If AI search is active and we have semantic results, return them directly |
| 304 | + // They are already full class objects sorted by semantic score from the backend |
300 | 305 | if (aiSearchActive && semanticResults.length > 0) { |
301 | | - // Backend already applies threshold filtering and sorting |
302 | | - // We need to maintain the order from API response |
303 | | - const classMap = new Map( |
304 | | - includedClasses.map((cls) => [ |
305 | | - `${cls.subject}-${cls.courseNumber}`, |
306 | | - cls, |
307 | | - ]) |
308 | | - ); |
309 | | - |
310 | | - // Map semantic results to actual class objects, preserving order |
311 | | - const filtered = semanticResults |
312 | | - .map((r) => classMap.get(`${r.subject}-${r.courseNumber}`)) |
313 | | - .filter((cls) => cls !== undefined); |
314 | | - |
315 | | - return filtered; |
| 306 | + return semanticResults; |
316 | 307 | } |
317 | 308 |
|
318 | 309 | // Otherwise use normal fuzzy search |
@@ -385,10 +376,41 @@ export default function ClassBrowser({ |
385 | 376 | throw new Error("Semantic search failed"); |
386 | 377 | } |
387 | 378 |
|
388 | | - const data = await response.json(); |
389 | | - setSemanticResults(data.results || []); |
390 | | - } catch (error) { |
391 | | - console.error("Semantic search error:", error); |
| 379 | + const responseData = await response.json(); |
| 380 | + const identifiers: Array<{ subject: string; courseNumber: string; score: number }> = |
| 381 | + responseData.results || []; |
| 382 | + |
| 383 | + if (identifiers.length === 0) { |
| 384 | + setSemanticResults([]); |
| 385 | + return; |
| 386 | + } |
| 387 | + |
| 388 | + const { data: gqlData } = await apolloClient.query({ |
| 389 | + query: GET_CLASSES_BY_IDENTIFIERS, |
| 390 | + variables: { |
| 391 | + year: currentYear, |
| 392 | + semester: currentSemester, |
| 393 | + identifiers: identifiers.map((r) => ({ |
| 394 | + subject: r.subject, |
| 395 | + courseNumber: r.courseNumber, |
| 396 | + })), |
| 397 | + }, |
| 398 | + fetchPolicy: "no-cache", |
| 399 | + }); |
| 400 | + |
| 401 | + const classesById = new Map( |
| 402 | + (gqlData?.classesByIdentifiers ?? []).map((cls: ICatalogClass) => [ |
| 403 | + `${cls.subject}-${cls.courseNumber}`, |
| 404 | + cls, |
| 405 | + ]) |
| 406 | + ); |
| 407 | + |
| 408 | + const sorted = identifiers |
| 409 | + .map((r) => classesById.get(`${r.subject}-${r.courseNumber}`)) |
| 410 | + .filter((cls): cls is ICatalogClass => cls !== undefined); |
| 411 | + |
| 412 | + setSemanticResults(sorted); |
| 413 | + } catch { |
392 | 414 | setSemanticResults([]); |
393 | 415 | } finally { |
394 | 416 | setSemanticLoading(false); |
|
0 commit comments