Skip to content

Commit 96913e4

Browse files
committed
refactor(ai-ui): simplify ask-ai rendering to text-only responses
1 parent dd7a7dc commit 96913e4

4 files changed

Lines changed: 22 additions & 400 deletions

File tree

packages/docsearch-react/src/AskAiScreen.tsx

Lines changed: 10 additions & 214 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,12 @@
1-
import type { UseChatHelpers } from '@ai-sdk/react';
21
import 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';
64
import { MemoizedMarkdown } from './MemoizedMarkdown';
75
import type { ScreenStateProps } from './ScreenState';
86
import type { StoredSearchPlugin } from './stored-searches';
9-
import { ToolCall } from './ToolCall';
107
import type { InternalDocSearchHit, StoredAskAiState } from './types';
11-
import type { AIMessage } from './types/AskiAi';
8+
import type { AIMessage, AskAiStatus } from './types/AskiAi';
129
import { extractLinksFromMessage, getMessageContent, isThreadDepthError } from './utils/ai';
13-
import { groupConsecutiveToolResults } from './utils/groupConsecutiveToolResults';
1410

1511
export 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

6641
type 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

7549
interface 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

10172
function 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

268175
export 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

Comments
 (0)