Skip to content

Commit 2b4779a

Browse files
authored
feat(suggested-questions): Add suggested questions (#2787)
* feat(suggested-questions): Add suggested questions * fix: build * add docs, fix react-example * add some more documentation
1 parent f7be2a6 commit 2b4779a

11 files changed

Lines changed: 126 additions & 21 deletions

File tree

packages/docsearch-css/src/modal.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1426,6 +1426,7 @@ assistive tech users */
14261426
justify-content: center;
14271427
background-color: #fff;
14281428
color: var(--docsearch-text-color);
1429+
cursor: pointer;
14291430
}
14301431

14311432
.DocSearch-Menu {

packages/docsearch-react/src/DocSearch.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,12 @@ export type DocSearchAskAi = {
6161
* The search parameters to use for the ask AI feature.
6262
*/
6363
searchParameters?: AskAiSearchParameters;
64+
/**
65+
* Enables displaying suggested questions on Ask AI's new conversation screen.
66+
*
67+
* @default false
68+
*/
69+
suggestedQuestions?: boolean;
6470
};
6571

6672
export interface DocSearchIndex {

packages/docsearch-react/src/DocSearchModal.tsx

Lines changed: 42 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
type AutocompleteState,
77
} from '@algolia/autocomplete-core';
88
import { useTheme } from '@docsearch/core/useTheme';
9+
import type { ChatRequestOptions } from 'ai';
910
import { DefaultChatTransport, lastAssistantMessageIsCompleteWithToolCalls } from 'ai';
1011
import type { SearchResponse } from 'algoliasearch/lite';
1112
import React, { type JSX } from 'react';
@@ -22,9 +23,17 @@ import { ScreenState } from './ScreenState';
2223
import type { SearchBoxTranslations } from './SearchBox';
2324
import { SearchBox } from './SearchBox';
2425
import { createStoredConversations, createStoredSearches } from './stored-searches';
25-
import type { DocSearchHit, DocSearchState, InternalDocSearchHit, StoredAskAiState, StoredDocSearchHit } from './types';
26+
import type {
27+
DocSearchHit,
28+
DocSearchState,
29+
InternalDocSearchHit,
30+
StoredAskAiState,
31+
StoredDocSearchHit,
32+
SuggestedQuestionHit,
33+
} from './types';
2634
import type { AIMessage, AskAiState } from './types/AskiAi';
2735
import { useSearchClient } from './useSearchClient';
36+
import { useSuggestedQuestions } from './useSuggestedQuestions';
2837
import { useTouchEvents } from './useTouchEvents';
2938
import { useTrapFocus } from './useTrapFocus';
3039
import { groupBy, identity, noop, removeHighlightTags, isModifierEvent, scrollTo as scrollToUtils } from './utils';
@@ -341,6 +350,11 @@ export function DocSearchModal({
341350
const askAiConfigurationId = typeof askAi === 'string' ? askAi : askAiConfig?.assistantId || null;
342351
const askAiSearchParameters = askAiConfig?.searchParameters;
343352
const [askAiState, setAskAiState] = React.useState<AskAiState>('initial');
353+
const suggestedQuestions = useSuggestedQuestions({
354+
assistantId: askAiConfigurationId,
355+
searchClient,
356+
suggestedQuestionsEnabled: askAiConfig?.suggestedQuestions,
357+
});
344358

345359
// Format the `indexes` to be used until `indexName` and `searchParameters` props are fully removed.
346360
const indexes: DocSearchIndex[] = [];
@@ -517,24 +531,35 @@ export function DocSearchModal({
517531
>(undefined);
518532

519533
const handleSelectAskAiQuestion = React.useCallback(
520-
(toggle: boolean, query: string) => {
534+
(toggle: boolean, query: string, suggestedQuestion: SuggestedQuestionHit | undefined = undefined) => {
521535
if (toggle && askAiState === 'new-conversation') {
522536
// We're starting a new conversation, clear out current messages
523537
setMessages([]);
524538
setAskAiState('initial');
525539
}
526540

541+
const messageOptions: ChatRequestOptions = {};
542+
543+
if (suggestedQuestion) {
544+
messageOptions.body = {
545+
suggestedQuestionId: suggestedQuestion.objectID,
546+
};
547+
}
548+
527549
onAskAiToggle(toggle);
528550
setStoppedStream(false);
529-
sendMessage({
530-
role: 'user',
531-
parts: [
532-
{
533-
type: 'text',
534-
text: query,
535-
},
536-
],
537-
});
551+
sendMessage(
552+
{
553+
role: 'user',
554+
parts: [
555+
{
556+
type: 'text',
557+
text: query,
558+
},
559+
],
560+
},
561+
messageOptions,
562+
);
538563

539564
if (dropdownRef.current) {
540565
// some test environments (like jsdom) don't implement element.scrollTo
@@ -809,6 +834,10 @@ export function DocSearchModal({
809834
setAskAiState('conversation-history');
810835
};
811836

837+
const selectSuggestedQuestion = (suggestedQuestion: SuggestedQuestionHit): void => {
838+
handleSelectAskAiQuestion(true, suggestedQuestion.question, suggestedQuestion);
839+
};
840+
812841
// hide the dropdown on idle and no collections
813842
let showDocsearchDropdown = true;
814843
const hasCollections = state.collections.some((collection) => collection.items.length > 0);
@@ -885,6 +914,8 @@ export function DocSearchModal({
885914
hasCollections={hasCollections}
886915
askAiState={askAiState}
887916
selectAskAiQuestion={handleSelectAskAiQuestion}
917+
suggestedQuestions={suggestedQuestions}
918+
selectSuggestedQuestion={selectSuggestedQuestion}
888919
onAskAiToggle={onAskAiToggle}
889920
onItemClick={(item, event) => {
890921
// if the item is askAI toggle the screen

packages/docsearch-react/src/NewConversationScreen.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
1-
import type { Hit } from 'algoliasearch/lite';
21
import React, { type JSX } from 'react';
32

3+
import type { SuggestedQuestionHit } from './types';
4+
45
export type NewConversationTranslations = Partial<{
56
newConversationTitle: string;
67
newConversationDescription: string;
78
}>;
89

9-
type SuggestedQuestion = Hit<{ question: string }>;
10-
1110
interface NewConversationProps {
12-
suggestedQuestions?: SuggestedQuestion[];
13-
selectSuggestedQuestion: (query: string) => void;
11+
suggestedQuestions?: SuggestedQuestionHit[];
12+
selectSuggestedQuestion: (question: SuggestedQuestionHit) => void;
1413
translations?: NewConversationTranslations;
1514
}
1615

@@ -35,7 +34,7 @@ export function NewConversationScreen({
3534
key={question.objectID}
3635
type="button"
3736
className="DocSearch-NewConversationScreen-SuggestedQuestion"
38-
onClick={() => selectSuggestedQuestion(question.question)}
37+
onClick={() => selectSuggestedQuestion(question)}
3938
>
4039
{question.question}
4140
</button>

packages/docsearch-react/src/ScreenState.tsx

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { ResultsScreen } from './ResultsScreen';
1717
import type { StartScreenTranslations } from './StartScreen';
1818
import { StartScreen } from './StartScreen';
1919
import type { StoredSearchPlugin } from './stored-searches';
20-
import type { InternalDocSearchHit, StoredAskAiState, StoredDocSearchHit } from './types';
20+
import type { InternalDocSearchHit, StoredAskAiState, StoredDocSearchHit, SuggestedQuestionHit } from './types';
2121
import type { AIMessage, AskAiState } from './types/AskiAi';
2222

2323
export type ScreenStateTranslations = Partial<{
@@ -54,6 +54,8 @@ export interface ScreenStateProps<TItem extends BaseItem>
5454
onFeedback?: (messageId: string, thumbs: 0 | 1) => Promise<void>;
5555
askAiState: AskAiState;
5656
selectAskAiQuestion: (toggle: boolean, query: string) => void;
57+
suggestedQuestions: SuggestedQuestionHit[];
58+
selectSuggestedQuestion: (question: SuggestedQuestionHit) => void;
5759
}
5860

5961
export const ScreenState = React.memo(
@@ -66,9 +68,8 @@ export const ScreenState = React.memo(
6668
return (
6769
<NewConversationScreen
6870
translations={translations?.newConversation}
69-
selectSuggestedQuestion={(query: string) => {
70-
props.selectAskAiQuestion(true, query);
71-
}}
71+
selectSuggestedQuestion={props.selectSuggestedQuestion}
72+
suggestedQuestions={props.suggestedQuestions}
7273
/>
7374
);
7475
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export const MAX_QUERY_SIZE = 512;
22
export const ASK_AI_API_URL = 'https://askai.algolia.com/chat';
33
export const USE_ASK_AI_TOKEN = true;
4+
export const SUGGESTED_QUETIONS_INDEX_NAME = 'algolia_ask_ai_suggested_questions';
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import type { Hit } from 'algoliasearch/lite';
2+
3+
export type SuggestedQuestion = {
4+
appId: string;
5+
assistantId: string;
6+
question: string;
7+
locale?: string;
8+
state: 'published';
9+
source: string;
10+
order: number;
11+
};
12+
13+
export type SuggestedQuestionHit = Hit<SuggestedQuestion>;

packages/docsearch-react/src/types/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ export * from './DocSearchTheme';
44
export * from './InternalDocSearchHit';
55
export * from './KeyboardShortcuts';
66
export * from './StoredDocSearchHit';
7+
export * from './SuggestedQuestions';
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import type { SearchResponse } from 'algoliasearch/lite';
2+
import { useEffect, useState } from 'react';
3+
4+
import { SUGGESTED_QUETIONS_INDEX_NAME } from './constants';
5+
6+
import type { DocSearchTransformClient, SuggestedQuestion, SuggestedQuestionHit } from '.';
7+
8+
type UseSuggestedQuestionsProps = {
9+
assistantId: string | null;
10+
searchClient: DocSearchTransformClient;
11+
suggestedQuestionsEnabled?: boolean;
12+
};
13+
14+
export const useSuggestedQuestions = ({
15+
assistantId,
16+
searchClient,
17+
suggestedQuestionsEnabled = false,
18+
}: UseSuggestedQuestionsProps): SuggestedQuestionHit[] => {
19+
const [suggestedQuestions, setSuggestedQuestions] = useState<SuggestedQuestionHit[]>([]);
20+
21+
useEffect(() => {
22+
const getSuggestedQuestions = async (): Promise<void> => {
23+
const { results } = await searchClient.search<SuggestedQuestion>({
24+
requests: [
25+
{
26+
indexName: SUGGESTED_QUETIONS_INDEX_NAME,
27+
filters: `state:published AND assistantId:${assistantId}`,
28+
hitsPerPage: 3,
29+
},
30+
],
31+
});
32+
33+
const result = results[0] as SearchResponse<SuggestedQuestion>;
34+
35+
setSuggestedQuestions(result.hits);
36+
};
37+
38+
if (suggestedQuestionsEnabled && assistantId && assistantId !== '') {
39+
getSuggestedQuestions();
40+
}
41+
}, [suggestedQuestionsEnabled, assistantId, searchClient]);
42+
43+
return suggestedQuestions;
44+
};

packages/website/docs/api.mdx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,9 @@ docsearch({
196196
// Deduplication
197197
distinct: true,
198198
},
199+
200+
// Enables/disables showing suggested questions on Ask AI's new conversation screen
201+
suggestedQuestions: true,
199202
},
200203
// ...
201204
});
@@ -234,6 +237,9 @@ in case you want to use different credentials for askAi
234237
// Deduplication
235238
distinct: true,
236239
},
240+
241+
// Enables/disables showing suggested questions on Ask AI's new conversation screen
242+
suggestedQuestions: true,
237243
}}
238244
/>
239245
```

0 commit comments

Comments
 (0)