Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion packages/gitbook/src/components/AI/useAIChat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import * as zustand from 'zustand';

import { useCurrentContent } from '@/components/hooks';
import { useLanguage } from '@/intl/client';
import { tString } from '@/intl/translate';
import {
Expand All @@ -15,6 +16,7 @@ import assertNever from 'assert-never';
import * as React from 'react';
import { getInsightsSession, useTrackEvent } from '../Insights';
import { useSetSearchState } from '../Search';
import { addRecentSearchQuery } from '../Search/recent-queries';
import type { AnyAIControl } from './controls';
import { ConfirmControlDef, ConfirmControlOutputSchema } from './controls/ConfirmControl';
import { type RenderAIMessageOptions, streamAIChatResponse } from './server-actions';
Expand Down Expand Up @@ -170,6 +172,7 @@ export function AIChatProvider(props: {
const messageContextRef = useAIMessageContextRef();
const trackEvent = useTrackEvent();
const setSearchState = useSetSearchState();
const { siteSpaceId } = useCurrentContent();
const language = useLanguage();

// Event listeners storage
Expand Down Expand Up @@ -465,6 +468,10 @@ export function AIChatProvider(props: {

// For first message, update the ask parameter in URL
if (messages.length === 0) {
if (siteSpaceId) {
addRecentSearchQuery(siteSpaceId, input.message, 'ask');
}

setSearchState((prev) => ({
ask: input.message,
query: prev?.query ?? null,
Expand Down Expand Up @@ -508,7 +515,7 @@ export function AIChatProvider(props: {

streamResponse({ message: input.message });
},
[setSearchState, trackEvent, streamResponse, language]
[setSearchState, siteSpaceId, trackEvent, streamResponse, language]
);

// Clear the conversation and reset ask parameter
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,39 @@
import { useCurrentContent } from '@/components/hooks';
import { tString, useLanguage } from '@/intl/client';
import type { AIChatController } from '../AI';
import { useRecentSearchQueries } from '../Search/recent-queries';
import { Button } from '../primitives';

export default function AIChatSuggestedQuestions(props: {
chatController: AIChatController;
suggestions?: string[];
}) {
const language = useLanguage();
const { chatController, suggestions: _suggestions } = props;
const { siteSpaceId } = useCurrentContent();
const recentQueries = useRecentSearchQueries(siteSpaceId ?? '');
const { chatController, suggestions: configuredSuggestions } = props;

const suggestions =
_suggestions && _suggestions.length > 0
? _suggestions
: [
tString(language, 'ai_chat_suggested_questions_about_this_page'),
tString(language, 'ai_chat_suggested_questions_read_next'),
tString(language, 'ai_chat_suggested_questions_example'),
];
const defaultSuggestions = [
tString(language, 'ai_chat_suggested_questions_about_this_page'),
tString(language, 'ai_chat_suggested_questions_read_next'),
tString(language, 'ai_chat_suggested_questions_example'),
];
const baseSuggestions =
configuredSuggestions && configuredSuggestions.length > 0
? configuredSuggestions
: defaultSuggestions;

const suggestions = [
...recentQueries.filter((entry) => entry.action === 'ask').map((entry) => entry.query),
...baseSuggestions,
].reduce<string[]>((acc, suggestion) => {
if (acc.includes(suggestion)) {
return acc;
}

acc.push(suggestion);
return acc;
}, []);

return (
<div
Expand Down
6 changes: 6 additions & 0 deletions packages/gitbook/src/components/Search/SearchAskBar.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
'use client';

import type { Assistant } from '@/components/AI';
import { useCurrentContent } from '@/components/hooks';
import { t, tString, useLanguage } from '@/intl/client';
import { KeyboardShortcut } from '../primitives/KeyboardShortcut';
import { SearchResultItem } from './SearchResultItem';
import { addRecentSearchQuery } from './recent-queries';
import { useSearchLink } from './useSearch';

/**
Expand All @@ -19,6 +21,7 @@ export function SearchAskBar(props: {
}) {
const { query, assistant, active = false, withShortcut = false, onSelect } = props;
const language = useLanguage();
const { siteSpaceId } = useCurrentContent();
const getSearchLinkProps = useSearchLink();

const linkProps = getSearchLinkProps(
Expand All @@ -28,6 +31,9 @@ export function SearchAskBar(props: {
open: assistant.mode === 'search',
},
() => {
if (assistant.mode === 'search' && siteSpaceId) {
addRecentSearchQuery(siteSpaceId, query, 'ask');
}
onSelect?.();
assistant.open(query);
}
Expand Down
40 changes: 27 additions & 13 deletions packages/gitbook/src/components/Search/SearchQuestionResultItem.tsx
Original file line number Diff line number Diff line change
@@ -1,43 +1,57 @@
import React from 'react';

import type { Assistant } from '@/components/AI';
import { useCurrentContent } from '@/components/hooks';
import { tString, useLanguage } from '@/intl/client';
import { SearchResultItem } from './SearchResultItem';
import { addRecentSearchQuery } from './recent-queries';
import { useSearchLink } from './useSearch';

export const SearchQuestionResultItem = React.forwardRef(function SearchQuestionResultItem(
props: {
question: string;
action: 'ask' | 'search';
active: boolean;
recommended?: boolean;
assistant: Assistant;
style?: React.CSSProperties;
},
ref: React.Ref<HTMLAnchorElement>
) {
const { question, recommended = false, active, assistant, style, ...rest } = props;
const { question, action, active, assistant, ...rest } = props;
const language = useLanguage();
const { siteSpaceId } = useCurrentContent();
const getLinkProp = useSearchLink();
const shouldAsk = action === 'ask';

return (
<SearchResultItem
size="small"
action={tString(language, 'ask', '')}
action={tString(language, shouldAsk ? 'ask' : 'search')}
ref={ref}
data-testid="search-recommended-question"
scroll={false}
{...getLinkProp(
{
ask: question,
query: null,
open: assistant.mode === 'search',
},
() => {
assistant.open(question);
}
shouldAsk
? {
ask: question,
query: null,
open: assistant.mode === 'search',
}
: {
ask: null,
query: question,
open: true,
},
shouldAsk
? () => {
if (assistant.mode === 'search' && siteSpaceId) {
addRecentSearchQuery(siteSpaceId, question, 'ask');
}
assistant.open(question);
}
: undefined
)}
active={active}
leadingIcon="search"
leadingIcon={shouldAsk ? assistant.icon : 'search'}
{...rest}
>
{question}
Expand Down
31 changes: 19 additions & 12 deletions packages/gitbook/src/components/Search/SearchResults.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { AnimatePresence, motion } from 'framer-motion';
import React from 'react';

import { useAI } from '@/components/AI';
import { useCurrentContent } from '@/components/hooks';
import { t, useLanguage } from '@/intl/client';
import { tcls } from '@/lib/tailwind';

Expand All @@ -13,18 +14,13 @@ import { SearchPageResultItem } from './SearchPageResultItem';
import { SearchQuestionResultItem } from './SearchQuestionResultItem';
import { SearchRecordResultItem } from './SearchRecordResultItem';
import { SearchResultItem } from './SearchResultItem';
import type { OrderedComputedResult } from './search-types';
import type { LocalPageResult } from './useLocalSearchResults';
import { addRecentSearchQuery } from './recent-queries';
import type { ResultType } from './useSearchResults';

export interface SearchResultsRef {
select(): boolean;
}

type ResultType =
| OrderedComputedResult
| LocalPageResult
| { type: 'recommended-question'; id: string; question: string };

function getResultKey(item: ResultType): string {
switch (item.type) {
case 'local-page':
Expand Down Expand Up @@ -61,6 +57,7 @@ export const SearchResults = React.forwardRef(function SearchResults(
const { children, id, query, results, fetching, cursor, error, onResultSelect } = props;

const language = useLanguage();
const { siteSpaceId } = useCurrentContent();
const shouldAnimateResults = !query || fetching;
const previousCursor = React.useRef<number | null>(cursor);
const seenResultKeys = React.useRef(new Set<string>());
Expand Down Expand Up @@ -183,11 +180,24 @@ export const SearchResults = React.forwardRef(function SearchResults(
const itemKey = getResultKey(item);
const shouldAnimateItem =
shouldAnimateResults || !seenResultKeys.current.has(itemKey);
const handleResultSelect = () => {
if (
query &&
siteSpaceId &&
(item.type === 'local-page' ||
item.type === 'page' ||
item.type === 'record')
) {
addRecentSearchQuery(siteSpaceId, query, 'search');
}

onResultSelect?.();
};
const resultItemProps = {
'aria-posinset': index + 1,
'aria-setsize': results.length,
id: `${id}-${index}`,
onClickCapture: () => onResultSelect?.(),
onClickCapture: handleResultSelect,
};
switch (item.type) {
case 'local-page':
Expand Down Expand Up @@ -253,12 +263,9 @@ export const SearchResults = React.forwardRef(function SearchResults(
refs.current[index] = ref;
}}
question={item.question}
action={item.action}
active={index === cursor}
assistant={primaryAssistant}
recommended
style={{
animationDelay: `${index * 25}ms,${100 + index * 25}ms`,
}}
{...resultItemProps}
/>
</motion.div>
Expand Down
62 changes: 62 additions & 0 deletions packages/gitbook/src/components/Search/empty-search-results.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import type { RecentSearchQueryEntry } from './recent-queries';

export type RecommendedQuestionResult = {
type: 'recommended-question';
id: string;
question: string;
action: 'ask' | 'search';
};

export function createRecommendedQuestionResult(
id: string,
question: string,
action: 'ask' | 'search' = 'ask'
): RecommendedQuestionResult {
return {
type: 'recommended-question',
id,
question,
action,
};
}

export function getEmptySearchResults(props: {
withAI: boolean;
recentQueries: RecentSearchQueryEntry[];
recommendedQuestions: RecommendedQuestionResult[];
}): RecommendedQuestionResult[] {
const { withAI, recentQueries, recommendedQuestions } = props;

if (!withAI) {
return [];
}

const seenQuestions = new Set<string>();
const results: RecommendedQuestionResult[] = [];

for (const recentQuery of recentQueries) {
if (seenQuestions.has(recentQuery.query)) {
continue;
}

seenQuestions.add(recentQuery.query);
results.push(
createRecommendedQuestionResult(
`recent-query-${recentQuery.action}-${recentQuery.query}`,
recentQuery.query,
recentQuery.action
)
);
}

for (const question of recommendedQuestions) {
if (seenQuestions.has(question.question)) {
continue;
}

seenQuestions.add(question.question);
results.push(question);
}

return results;
}
Loading
Loading