@@ -30,6 +30,7 @@ type AIQueryResultsProps = {
3030 askAIEventGroupId : React . MutableRefObject < string >
3131 aiCouldNotAnswer : boolean
3232 setAICouldNotAnswer : ( aiCouldNotAnswer : boolean ) => void
33+ listElementsRef : React . RefObject < Array < HTMLLIElement | null > >
3334}
3435
3536type AISearchResultEventParams = {
@@ -56,6 +57,7 @@ export function AskAIResults({
5657 askAIEventGroupId,
5758 aiCouldNotAnswer,
5859 setAICouldNotAnswer,
60+ listElementsRef,
5961} : AIQueryResultsProps ) {
6062 const router = useRouter ( )
6163 const { t } = useTranslation ( 'search' )
@@ -78,27 +80,30 @@ export function AskAIResults({
7880
7981 const [ conversationId , setConversationId ] = useState < string > ( '' )
8082
81- const handleAICannotAnswer = ( passedConversationId ?: string ) => {
83+ const handleAICannotAnswer = (
84+ passedConversationId ?: string ,
85+ statusCode = 400 ,
86+ uiMessage = t ( 'search.ai.responses.unable_to_answer' ) ,
87+ ) => {
8288 setInitialLoading ( false )
8389 setResponseLoading ( false )
8490 setAICouldNotAnswer ( true )
85- const cannedResponse = t ( 'search.ai.unable_to_answer' )
8691 sendAISearchResultEvent ( {
8792 sources : [ ] ,
88- message : cannedResponse ,
93+ message : uiMessage ,
8994 eventGroupId : askAIEventGroupId . current ,
9095 couldNotAnswer : true ,
91- status : 400 ,
96+ status : statusCode ,
9297 connectedEventId : passedConversationId || conversationId ,
9398 } )
94- setMessage ( cannedResponse )
95- setAnnouncement ( cannedResponse )
99+ setMessage ( uiMessage )
100+ setAnnouncement ( uiMessage )
96101 setReferences ( [ ] )
97102 setItem (
98103 query ,
99104 {
100105 query,
101- message : cannedResponse ,
106+ message : uiMessage ,
102107 sources : [ ] ,
103108 aiCouldNotAnswer : true ,
104109 connectedEventId : passedConversationId || conversationId ,
@@ -156,17 +161,44 @@ export function AskAIResults({
156161 try {
157162 const response = await executeAISearch ( router , version , query , debug )
158163 if ( ! response . ok ) {
159- console . error (
160- `Failed to fetch search results.\nStatus ${ response . status } \n${ response . statusText } ` ,
161- )
162- sendAISearchResultEvent ( {
163- sources : [ ] ,
164- message : '' ,
165- eventGroupId : askAIEventGroupId . current ,
166- couldNotAnswer : false ,
167- status : response . status ,
168- } )
169- return setAISearchError ( )
164+ // If there is JSON and the `upstreamStatus` key, the error is from the upstream sever (CSE)
165+ let responseJson
166+ try {
167+ responseJson = await response . json ( )
168+ } catch ( error ) {
169+ console . error ( 'Failed to parse JSON:' , error )
170+ }
171+ const upstreamStatus = responseJson ?. upstreamStatus
172+ // If there is no upstream status, the error is either on our end or a 500 from CSE, so we can show the error
173+ if ( ! upstreamStatus ) {
174+ console . error (
175+ `Failed to fetch search results.\nStatus ${ response . status } \n${ response . statusText } ` ,
176+ )
177+ sendAISearchResultEvent ( {
178+ sources : [ ] ,
179+ message : '' ,
180+ eventGroupId : askAIEventGroupId . current ,
181+ couldNotAnswer : false ,
182+ status : response . status ,
183+ } )
184+ return setAISearchError ( )
185+ // Query invalid - either sensitive question or spam
186+ } else if ( upstreamStatus === 400 || upstreamStatus === 422 ) {
187+ return handleAICannotAnswer ( '' , upstreamStatus , t ( 'search.ai.responses.invalid_query' ) )
188+ // Query too large
189+ } else if ( upstreamStatus === 413 ) {
190+ return handleAICannotAnswer (
191+ '' ,
192+ upstreamStatus ,
193+ t ( 'search.ai.responses.query_too_large' ) ,
194+ )
195+ } else if ( upstreamStatus === 429 ) {
196+ return handleAICannotAnswer (
197+ '' ,
198+ upstreamStatus ,
199+ t ( 'search.ai.responses.asked_too_many_times' ) ,
200+ )
201+ }
170202 } else {
171203 setAISearchError ( false )
172204 }
@@ -209,7 +241,7 @@ export function AskAIResults({
209241 return
210242 }
211243 } catch ( e ) {
212- console . error (
244+ console . warn (
213245 'Failed to parse JSON:' ,
214246 e ,
215247 'Line:' ,
@@ -226,7 +258,7 @@ export function AskAIResults({
226258 setConversationId ( parsedLine . conversation_id )
227259 } else if ( parsedLine . chunkType === 'NO_CONTENT_SIGNAL' ) {
228260 // Serve canned response. A question that cannot be answered was asked
229- handleAICannotAnswer ( conversationIdBuffer )
261+ handleAICannotAnswer ( conversationIdBuffer , 200 )
230262 } else if ( parsedLine . chunkType === 'SOURCES' ) {
231263 if ( ! isCancelled ) {
232264 sourcesBuffer = sourcesBuffer . concat ( parsedLine . sources )
@@ -240,7 +272,11 @@ export function AskAIResults({
240272 }
241273 } else if ( parsedLine . chunkType === 'INPUT_CONTENT_FILTER' ) {
242274 // Serve canned response. A spam question was asked
243- handleAICannotAnswer ( conversationIdBuffer )
275+ handleAICannotAnswer (
276+ conversationIdBuffer ,
277+ 200 ,
278+ t ( 'search.ai.responses.invalid_query' ) ,
279+ )
244280 }
245281 if ( ! isCancelled ) {
246282 setAnnouncement ( 'Copilot Response Loading...' )
@@ -396,6 +432,7 @@ export function AskAIResults({
396432 if ( index >= MAX_REFERENCES_TO_SHOW ) {
397433 return null
398434 }
435+ const refIndex = index + referencesIndexOffset
399436 return (
400437 < ActionList . Item
401438 sx = { {
@@ -408,7 +445,12 @@ export function AskAIResults({
408445 onSelect = { ( ) => {
409446 referenceOnSelect ( source . url )
410447 } }
411- active = { index + referencesIndexOffset === selectedIndex }
448+ active = { refIndex === selectedIndex }
449+ ref = { ( element ) => {
450+ if ( listElementsRef . current ) {
451+ listElementsRef . current [ refIndex ] = element
452+ }
453+ } }
412454 >
413455 < ActionList . LeadingVisual aria-hidden = "true" >
414456 < FileIcon />
0 commit comments