@@ -7,11 +7,10 @@ import type {
77 ICatalogFilterOptions ,
88 ICatalogFilters ,
99} from "@/lib/api/catalog" ;
10- import {
11- GetCatalogFilterOptionsDocument ,
12- GetCatalogSearchDocument ,
13- } from "@/lib/generated/graphql" ;
10+ import { GET_CATALOG_SEARCH } from "@/lib/api/catalog" ;
11+ import { GetCatalogFilterOptionsDocument } from "@/lib/generated/graphql" ;
1412import type {
13+ GetCatalogSearchQuery ,
1514 GetCatalogSearchQueryVariables ,
1615 Semester ,
1716} from "@/lib/generated/graphql" ;
@@ -56,6 +55,7 @@ export interface UseCatalogQueryOptions {
5655 sortBy : SortBy ;
5756 effectiveOrder : "asc" | "desc" ;
5857 filterVariables : ICatalogFilters | undefined ;
58+ semanticSearch ?: boolean ;
5959}
6060
6161export interface UseCatalogQueryReturn {
@@ -68,6 +68,7 @@ export interface UseCatalogQueryReturn {
6868 loadNextPage : ( ) => Promise < void > ;
6969 isLoadingNextPage : boolean ;
7070 filterOptions : ICatalogFilterOptions | null ;
71+ semanticError : string | null ;
7172}
7273
7374export default function useCatalogQuery ( {
@@ -77,19 +78,25 @@ export default function useCatalogQuery({
7778 sortBy,
7879 effectiveOrder,
7980 filterVariables,
81+ semanticSearch = false ,
8082} : UseCatalogQueryOptions ) : UseCatalogQueryReturn {
8183 const [ localPage , setLocalPage ] = useState ( 1 ) ;
8284 const [ classes , setClasses ] = useState < ICatalogClassServer [ ] > ( [ ] ) ;
8385 const [ isLoadingNextPage , setIsLoadingNextPage ] = useState ( false ) ;
8486 const isLoadingNextPageRef = useRef ( false ) ;
8587 const queryGenerationRef = useRef ( 0 ) ;
8688
87- // Use debounced search for the query
89+ // In semantic mode the query is committed externally — no debounce needed.
90+ // In normal mode, debounce to avoid firing on every keystroke.
8891 const [ debouncedQuery , setDebouncedQuery ] = useState ( rawQuery ) ;
8992 useEffect ( ( ) => {
93+ if ( semanticSearch ) {
94+ setDebouncedQuery ( rawQuery ) ;
95+ return ;
96+ }
9097 const timer = setTimeout ( ( ) => setDebouncedQuery ( rawQuery ) , 300 ) ;
9198 return ( ) => clearTimeout ( timer ) ;
92- } , [ rawQuery ] ) ;
99+ } , [ rawQuery , semanticSearch ] ) ;
93100
94101 // Reset page when filters/search change
95102 useEffect ( ( ) => {
@@ -104,6 +111,7 @@ export default function useCatalogQuery({
104111 effectiveOrder ,
105112 currentYear ,
106113 currentSemester ,
114+ semanticSearch ,
107115 ] ) ;
108116
109117 const catalogQueryVariables = useMemo <
@@ -116,6 +124,7 @@ export default function useCatalogQuery({
116124 filters : filterVariables ,
117125 sortBy : debouncedQuery ? undefined : mapSortBy ( sortBy ) ,
118126 sortOrder : debouncedQuery ? undefined : mapSortOrder ( effectiveOrder ) ,
127+ semanticSearch : semanticSearch || undefined ,
119128 } ) ,
120129 [
121130 currentYear ,
@@ -124,11 +133,12 @@ export default function useCatalogQuery({
124133 filterVariables ,
125134 sortBy ,
126135 effectiveOrder ,
136+ semanticSearch ,
127137 ]
128138 ) ;
129139
130140 // Server-side catalog query (always requests first page)
131- const { data, loading, fetchMore } = useQuery ( GetCatalogSearchDocument , {
141+ const { data, loading, error , fetchMore } = useQuery < GetCatalogSearchQuery , GetCatalogSearchQueryVariables > ( GET_CATALOG_SEARCH , {
132142 variables : {
133143 ...catalogQueryVariables ,
134144 page : 1 ,
@@ -203,6 +213,11 @@ export default function useCatalogQuery({
203213 } , [ catalogQueryVariables , fetchMore , hasNextPage , loading , localPage ] ) ;
204214
205215 const isFirstPageLoading = loading && localPage === 1 && ! isLoadingNextPage ;
216+ const semanticError =
217+ semanticSearch && error
218+ ? ( error . graphQLErrors [ 0 ] ?. message ?? error . message ?? "AI search failed" )
219+ : null ;
220+
206221 return {
207222 classes,
208223 loading : isFirstPageLoading ,
@@ -213,5 +228,6 @@ export default function useCatalogQuery({
213228 loadNextPage,
214229 isLoadingNextPage,
215230 filterOptions : filterOptionsData ?. catalogFilterOptions ?? null ,
231+ semanticError,
216232 } ;
217233}
0 commit comments