1- import type { UseChatHelpers } from '@ai-sdk/react' ;
21import React , { type JSX , useMemo , useState , useEffect } from 'react' ;
32
4- import { AggregatedSearchBlock } from './AggregatedSearchBlock' ;
5- import { AlertIcon , LoadingIcon } from './icons' ;
3+ import { AlertIcon } from './icons' ;
64import { MemoizedMarkdown } from './MemoizedMarkdown' ;
75import type { ScreenStateProps } from './ScreenState' ;
86import type { StoredSearchPlugin } from './stored-searches' ;
9- import { ToolCall } from './ToolCall' ;
107import type { InternalDocSearchHit , StoredAskAiState } from './types' ;
11- import type { AIMessage } from './types/AskiAi' ;
8+ import type { AIMessage , AskAiStatus } from './types/AskiAi' ;
129import { extractLinksFromMessage , getMessageContent , isThreadDepthError } from './utils/ai' ;
13- import { groupConsecutiveToolResults } from './utils/groupConsecutiveToolResults' ;
1410
1511export type AskAiScreenTranslations = Partial < {
1612 // Misc texts
@@ -24,27 +20,6 @@ export type AskAiScreenTranslations = Partial<{
2420 likeButtonTitle : string ;
2521 dislikeButtonTitle : string ;
2622 thanksForFeedbackText : string ;
27- // Tool call texts
28- preToolCallText : string ;
29- duringToolCallText : string ;
30- afterToolCallText : string ;
31- /**
32- * Build the full jsx element for the aggregated search block.
33- * If provided, completely overrides the default english renderer.
34- */
35- aggregatedToolCallNode ?: ( queries : string [ ] , onSearchQueryClick : ( query : string ) => void ) => React . ReactNode ;
36-
37- /**
38- * Generate the list connective parts only (backwards compatibility).
39- * Receives full list of queries and should return translation parts for before/after/separators.
40- * Example: (qs) => ({ before: 'searched for ', separator: ', ', lastSeparator: ' and ', after: '' }).
41- */
42- aggregatedToolCallText ?: ( queries : string [ ] ) => {
43- before ?: string ;
44- separator ?: string ;
45- lastSeparator ?: string ;
46- after ?: string ;
47- } ;
4823 /**
4924 * Message that's shown when user has stopped the streaming of a message.
5025 */
@@ -65,11 +40,10 @@ export type AskAiScreenTranslations = Partial<{
6540
6641type AskAiScreenProps = Omit < ScreenStateProps < InternalDocSearchHit > , 'translations' > & {
6742 messages : AIMessage [ ] ;
68- status : UseChatHelpers < AIMessage > [ 'status' ] ;
43+ status : AskAiStatus ;
6944 askAiError ?: Error ;
7045 translations ?: AskAiScreenTranslations ;
7146 onNewConversation : ( ) => void ;
72- agentStudio ?: boolean ;
7347} ;
7448
7549interface AskAiScreenHeaderProps {
@@ -90,55 +64,37 @@ interface AskAiExchangeCardProps {
9064 exchange : Exchange ;
9165 askAiError ?: Error ;
9266 isLastExchange : boolean ;
93- loadingStatus : UseChatHelpers < AIMessage > [ 'status' ] ;
94- onSearchQueryClick : ( query : string ) => void ;
67+ loadingStatus : AskAiStatus ;
9568 translations : AskAiScreenTranslations ;
9669 conversations : StoredSearchPlugin < StoredAskAiState > ;
97- onFeedback ?: ( messageId : string , thumbs : 0 | 1 ) => Promise < void > ;
98- agentStudio ?: boolean ;
9970}
10071
10172function AskAiExchangeCard ( {
10273 exchange,
10374 askAiError,
10475 isLastExchange,
10576 loadingStatus,
106- onSearchQueryClick,
10777 translations,
10878 conversations,
109- onFeedback,
110- agentStudio,
11179} : AskAiExchangeCardProps ) : JSX . Element {
11280 const { userMessage, assistantMessage } = exchange ;
11381
114- const {
115- stoppedStreamingText = 'You stopped this response' ,
116- errorTitleText = 'Chat error' ,
117- preToolCallText = 'Searching...' ,
118- afterToolCallText = 'Searched for' ,
119- duringToolCallText = 'Searching...' ,
120- } = translations ;
82+ const { stoppedStreamingText = 'You stopped this response' , errorTitleText = 'Chat error' } = translations ;
12183
12284 const isThreadDepth = isThreadDepthError ( askAiError ) ;
12385
12486 const assistantContent = useMemo ( ( ) => getMessageContent ( assistantMessage ) , [ assistantMessage ] ) ;
12587 const userContent = useMemo ( ( ) => getMessageContent ( userMessage ) , [ userMessage ] ) ;
12688
12789 const urlsToDisplay = React . useMemo ( ( ) => extractLinksFromMessage ( assistantMessage ) , [ assistantMessage ] ) ;
128-
129- const displayParts = React . useMemo ( ( ) => {
130- return groupConsecutiveToolResults ( assistantMessage ?. parts || [ ] ) ;
131- } , [ assistantMessage ] ) ;
90+ const displayParts = assistantMessage ?. parts || [ ] ;
13291
13392 const wasStopped = userMessage . metadata ?. stopped || assistantMessage ?. metadata ?. stopped ;
13493
13594 const showActions =
13695 ! wasStopped && ( ! isLastExchange || ( isLastExchange && loadingStatus === 'ready' && Boolean ( assistantMessage ) ) ) ;
13796
138- const isThinking =
139- [ 'submitted' , 'streaming' ] . includes ( loadingStatus ) &&
140- isLastExchange &&
141- ! displayParts . some ( ( part ) => part . type !== 'step-start' ) ;
97+ const isThinking = [ 'submitted' , 'streaming' ] . includes ( loadingStatus ) && isLastExchange && displayParts . length === 0 ;
14298
14399 return (
144100 < div className = "DocSearch-AskAiScreen-Response-Container" >
@@ -170,38 +126,6 @@ function AskAiExchangeCard({
170126 { displayParts . map ( ( part , idx ) => {
171127 const index = idx ;
172128
173- if ( typeof part === 'string' ) {
174- return (
175- < MemoizedMarkdown
176- key = { index }
177- content = { part }
178- copyButtonText = { translations . copyButtonText || 'Copy' }
179- copyButtonCopiedText = { translations . copyButtonCopiedText || 'Copied!' }
180- isStreaming = { loadingStatus === 'streaming' }
181- />
182- ) ;
183- }
184-
185- if ( part . type === 'aggregated-tool-call' ) {
186- return (
187- < AggregatedSearchBlock
188- key = { index }
189- queries = { part . queries }
190- translations = { translations }
191- onSearchQueryClick = { onSearchQueryClick }
192- />
193- ) ;
194- }
195-
196- if ( part . type === 'reasoning' && part . state === 'streaming' ) {
197- return (
198- < div key = { index } className = "DocSearch-AskAiScreen-MessageContent-Reasoning shimmer" >
199- < LoadingIcon className = "DocSearch-AskAiScreen-SmallerLoadingIcon" />
200- < span className = "shimmer" > Reasoning...</ span >
201- </ div >
202- ) ;
203- }
204-
205129 if ( part . type === 'text' ) {
206130 return (
207131 < MemoizedMarkdown
@@ -213,20 +137,7 @@ function AskAiExchangeCard({
213137 />
214138 ) ;
215139 }
216- if ( part . type === 'tool-searchIndex' || part . type === 'tool-algolia_search_index' ) {
217- return (
218- < ToolCall
219- key = { index }
220- translations = { {
221- preToolCallText,
222- searchingText : duringToolCallText ,
223- toolCallResultText : afterToolCallText ,
224- } }
225- part = { part }
226- onSearchQueryClick = { onSearchQueryClick }
227- />
228- ) ;
229- }
140+
230141 // fallback for unknown part type
231142 return null ;
232143 } ) }
@@ -241,8 +152,6 @@ function AskAiExchangeCard({
241152 latestAssistantMessageContent = { assistantContent ?. text || null }
242153 translations = { translations }
243154 conversations = { conversations }
244- agentStudio = { agentStudio }
245- onFeedback = { onFeedback }
246155 />
247156 </ div >
248157 </ div >
@@ -261,8 +170,6 @@ interface AskAiScreenFooterActionsProps {
261170 latestAssistantMessageContent : string | null ;
262171 translations : AskAiScreenTranslations ;
263172 conversations : StoredSearchPlugin < StoredAskAiState > ;
264- onFeedback ?: ( messageId : string , thumbs : 0 | 1 ) => Promise < void > ;
265- agentStudio ?: boolean ;
266173}
267174
268175export function AskAiScreenFooterActions ( {
@@ -271,65 +178,16 @@ export function AskAiScreenFooterActions({
271178 latestAssistantMessageContent,
272179 translations,
273180 conversations,
274- onFeedback,
275- agentStudio,
276181} : AskAiScreenFooterActionsProps ) : JSX . Element | null {
277- // local state for feedback, initialised from stored conversations
278- const initialFeedback = React . useMemo ( ( ) => {
279- const message = conversations . getOne ?.( id ) ;
280- return message ?. feedback ?? null ;
281- } , [ conversations , id ] ) ;
282-
283- const [ feedback , setFeedback ] = React . useState < 'dislike' | 'like' | null > ( initialFeedback ) ;
284- const [ saving , setSaving ] = React . useState ( false ) ;
285- const [ savingError , setSavingError ] = React . useState < Error | null > ( null ) ;
286-
287- const handleFeedback = async ( value : 'dislike' | 'like' ) : Promise < void > => {
288- if ( saving ) return ;
289- setSavingError ( null ) ;
290- setSaving ( true ) ;
291- try {
292- await onFeedback ?.( id , value === 'like' ? 1 : 0 ) ;
293- setFeedback ( value ) ;
294- } catch ( error ) {
295- setSavingError ( error as Error ) ;
296- } finally {
297- setSaving ( false ) ;
298- }
299- } ;
300-
301- const {
302- likeButtonTitle = 'Like' ,
303- dislikeButtonTitle = 'Dislike' ,
304- thanksForFeedbackText = 'Thanks for your feedback!' ,
305- } = translations ;
182+ void id ;
183+ void conversations ;
306184
307185 if ( ! showActions || ! latestAssistantMessageContent ) {
308186 return null ;
309187 }
310188
311189 return (
312190 < div className = "DocSearch-AskAiScreen-Actions" >
313- { ! agentStudio &&
314- ( feedback === null ? (
315- < >
316- { saving ? (
317- < LoadingIcon className = "DocSearch-AskAiScreen-SmallerLoadingIcon" />
318- ) : (
319- < >
320- < LikeButton title = { likeButtonTitle } onClick = { ( ) => handleFeedback ( 'like' ) } />
321- < DislikeButton title = { dislikeButtonTitle } onClick = { ( ) => handleFeedback ( 'dislike' ) } />
322- </ >
323- ) }
324- { savingError && (
325- < p className = "DocSearch-AskAiScreen-FeedbackText" > { savingError . message || 'An error occured' } </ p >
326- ) }
327- </ >
328- ) : (
329- < p className = "DocSearch-AskAiScreen-FeedbackText DocSearch-AskAiScreen-FeedbackText--visible" >
330- { thanksForFeedbackText }
331- </ p >
332- ) ) }
333191 < CopyButton
334192 translations = { translations }
335193 onClick = { ( ) => navigator . clipboard . writeText ( latestAssistantMessageContent ) }
@@ -407,11 +265,6 @@ export function AskAiScreen({ translations = {}, ...props }: AskAiScreenProps):
407265 return grouped ;
408266 } , [ messages , hasThreadDepthError ] ) ;
409267
410- const handleSearchQueryClick = ( query : string ) : void => {
411- props . onAskAiToggle ( false ) ;
412- props . setQuery ( query ) ;
413- } ;
414-
415268 // Only show the thread depth error if we have assistant messages
416269 const showThreadDepthError = hasThreadDepthError && messages . some ( ( m ) => m . role === 'assistant' ) ;
417270
@@ -448,9 +301,6 @@ export function AskAiScreen({ translations = {}, ...props }: AskAiScreenProps):
448301 loadingStatus = { props . status }
449302 translations = { translations }
450303 conversations = { props . conversations }
451- agentStudio = { props . agentStudio }
452- onSearchQueryClick = { handleSearchQueryClick }
453- onFeedback = { props . onFeedback }
454304 />
455305 ) ) }
456306 </ div >
@@ -550,57 +400,3 @@ export function CopyButton({
550400 </ button >
551401 ) ;
552402}
553-
554- export function LikeButton ( { title, onClick } : { title : string ; onClick : ( ) => void } ) : JSX . Element {
555- return (
556- < button
557- type = "button"
558- className = "DocSearch-AskAiScreen-ActionButton DocSearch-AskAiScreen-LikeButton"
559- title = { title }
560- onClick = { onClick }
561- >
562- < svg
563- xmlns = "http://www.w3.org/2000/svg"
564- width = "24"
565- height = "24"
566- viewBox = "0 0 24 24"
567- fill = "none"
568- stroke = "currentColor"
569- strokeWidth = "1.5"
570- strokeLinecap = "round"
571- strokeLinejoin = "round"
572- className = "lucide lucide-thumbs-up-icon lucide-thumbs-up"
573- >
574- < path d = "M7 10v12" />
575- < path d = "M15 5.88 14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2a3.13 3.13 0 0 1 3 3.88Z" />
576- </ svg >
577- </ button >
578- ) ;
579- }
580-
581- export function DislikeButton ( { title, onClick } : { title : string ; onClick : ( ) => void } ) : JSX . Element {
582- return (
583- < button
584- type = "button"
585- className = "DocSearch-AskAiScreen-ActionButton DocSearch-AskAiScreen-DislikeButton"
586- title = { title }
587- onClick = { onClick }
588- >
589- < svg
590- xmlns = "http://www.w3.org/2000/svg"
591- width = "24"
592- height = "24"
593- viewBox = "0 0 24 24"
594- fill = "none"
595- stroke = "currentColor"
596- strokeWidth = "1.5"
597- strokeLinecap = "round"
598- strokeLinejoin = "round"
599- className = "lucide lucide-thumbs-down-icon lucide-thumbs-down"
600- >
601- < path d = "M17 14V2" />
602- < path d = "M9 18.12 10 14H4.17a2 2 0 0 1-1.92-2.56l2.33-8A2 2 0 0 1 6.5 2H20a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-2.76a2 2 0 0 0-1.79 1.11L12 22a3.13 3.13 0 0 1-3-3.88Z" />
603- </ svg >
604- </ button >
605- ) ;
606- }
0 commit comments