diff --git a/.changeset/pretty-flies-jump.md b/.changeset/pretty-flies-jump.md
new file mode 100644
index 000000000..4062b1faf
--- /dev/null
+++ b/.changeset/pretty-flies-jump.md
@@ -0,0 +1,5 @@
+---
+'gitbook': minor
+---
+
+Improve the display of recommended questions by streaming them.
diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml
index d97078a53..23154b59f 100644
--- a/.github/workflows/ci.yaml
+++ b/.github/workflows/ci.yaml
@@ -192,10 +192,6 @@ jobs:
- deploy-v2-vercel
- deploy-v2-cloudflare
steps:
- - name: Checkout
- uses: actions/checkout@v4
- - name: Setup Bun
- uses: ./.github/composite/setup-bun
- name: Find GitHub Comment
uses: peter-evans/find-comment@v3
id: fc
diff --git a/bun.lock b/bun.lock
index 71370684c..8feb52c71 100644
--- a/bun.lock
+++ b/bun.lock
@@ -33,7 +33,7 @@
"name": "gitbook",
"version": "0.5.0",
"dependencies": {
- "@gitbook/api": "^0.89.0",
+ "@gitbook/api": "^0.90.0",
"@gitbook/cache-do": "workspace:*",
"@gitbook/emoji-codepoints": "workspace:*",
"@gitbook/icons": "workspace:*",
@@ -117,8 +117,9 @@
},
"packages/gitbook-v2": {
"name": "gitbook-v2",
+ "version": "0.0.0",
"dependencies": {
- "@gitbook/api": "^0.89.0",
+ "@gitbook/api": "^0.90.0",
"next": "canary",
"react": "^19.0.0",
"react-dom": "^19.0.0",
@@ -158,7 +159,7 @@
"name": "@gitbook/react-contentkit",
"version": "0.5.1",
"dependencies": {
- "@gitbook/api": "^0.89.0",
+ "@gitbook/api": "^0.90.0",
"assert-never": "^1.2.1",
"classnames": "^2.5.1",
},
@@ -565,7 +566,7 @@
"@fortawesome/fontawesome-svg-core": ["@fortawesome/fontawesome-svg-core@6.6.0", "", { "dependencies": { "@fortawesome/fontawesome-common-types": "6.6.0" } }, "sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg=="],
- "@gitbook/api": ["@gitbook/api@0.89.0", "", { "dependencies": { "event-iterator": "^2.0.0", "eventsource-parser": "^3.0.0" } }, "sha512-htzPY5OrFrZz29ShhB535e/j0Z6BBRDmdc97qfprJKzUE4zTRv2L20ZZ0dYeSgNFTkSiVhsuv0sfd6njWfNk7w=="],
+ "@gitbook/api": ["@gitbook/api@0.90.0", "", { "dependencies": { "event-iterator": "^2.0.0", "eventsource-parser": "^3.0.0" } }, "sha512-sLJj0JsC189a1PZ3a1LhtLDl0w7wkIBcWkhfoKNaz4gwoWg3cBBRt9wSqyK4nbshp0muRF1qFO3wA9vp+7LSdQ=="],
"@gitbook/cache-do": ["@gitbook/cache-do@workspace:packages/cache-do"],
diff --git a/packages/gitbook-v2/package.json b/packages/gitbook-v2/package.json
index db4cc5a3e..164cd1f53 100644
--- a/packages/gitbook-v2/package.json
+++ b/packages/gitbook-v2/package.json
@@ -5,7 +5,7 @@
"next": "canary",
"react": "^19.0.0",
"react-dom": "^19.0.0",
- "@gitbook/api": "^0.89.0"
+ "@gitbook/api": "^0.90.0"
},
"devDependencies": {
"@opennextjs/cloudflare": "^0.4.3"
diff --git a/packages/gitbook/package.json b/packages/gitbook/package.json
index 83c7726b3..3cd02978a 100644
--- a/packages/gitbook/package.json
+++ b/packages/gitbook/package.json
@@ -16,7 +16,7 @@
"clean": "rm -rf ./.next && rm -rf ./public/~gitbook/static"
},
"dependencies": {
- "@gitbook/api": "^0.89.0",
+ "@gitbook/api": "^0.90.0",
"@gitbook/cache-do": "workspace:*",
"@gitbook/emoji-codepoints": "workspace:*",
"@gitbook/icons": "workspace:*",
diff --git a/packages/gitbook/src/app/middleware/(space)/~gitbook/pdf/page.tsx b/packages/gitbook/src/app/middleware/(space)/~gitbook/pdf/page.tsx
index f98ef81f4..a155739d3 100644
--- a/packages/gitbook/src/app/middleware/(space)/~gitbook/pdf/page.tsx
+++ b/packages/gitbook/src/app/middleware/(space)/~gitbook/pdf/page.tsx
@@ -150,7 +150,7 @@ export default async function PDFHTMLOutput(props: {
) : null
}
diff --git a/packages/gitbook/src/components/Search/SearchModal.tsx b/packages/gitbook/src/components/Search/SearchModal.tsx
index ca165e731..c3233d148 100644
--- a/packages/gitbook/src/components/Search/SearchModal.tsx
+++ b/packages/gitbook/src/components/Search/SearchModal.tsx
@@ -18,7 +18,6 @@ import { SearchState, UpdateSearchState, useSearch } from './useSearch';
import { LoadingPane } from '../primitives/LoadingPane';
interface SearchModalProps {
- spaceId: string;
revisionId: string;
spaceTitle: string;
isMultiVariants: boolean;
@@ -147,7 +146,6 @@ function SearchModalBody(
) {
const {
pointer,
- spaceId,
revisionId,
spaceTitle,
withAsk,
@@ -309,7 +307,6 @@ function SearchModalBody(
,
) {
- const { children, query, pointer, spaceId, revisionId, withAsk, global, onSwitchToAsk } = props;
+ const { children, query, pointer, revisionId, withAsk, global, onSwitchToAsk } = props;
const language = useLanguage();
const trackEvent = useTrackEvent();
@@ -60,7 +66,6 @@ export const SearchResults = React.forwardRef(function SearchResults(
}>({ results: [], fetching: true });
const [cursor, setCursor] = React.useState(null);
const refs = React.useRef<(null | HTMLAnchorElement)[]>([]);
- const suggestedQuestionsRef = React.useRef(null);
React.useEffect(() => {
if (!query) {
@@ -69,42 +74,50 @@ export const SearchResults = React.forwardRef(function SearchResults(
return;
}
- if (suggestedQuestionsRef.current) {
- setResultsState({ results: suggestedQuestionsRef.current, fetching: false });
+ if (cachedRecommendedQuestions) {
+ setResultsState({ results: cachedRecommendedQuestions, fetching: false });
return;
}
let cancelled = false;
setResultsState({ results: [], fetching: true });
- getRecommendedQuestions(spaceId).then((questions) => {
- if (!questions) {
- if (!cancelled) {
- setResultsState({ results: [], fetching: false });
- }
- captureException(
- new Error(`corrupt-cache: getRecommendedQuestions is ${questions}`),
- );
- return;
- }
- const results = questions.map((question) => ({
- type: 'recommended-question',
- id: question,
- question: question,
- })) satisfies ResultType[];
-
- suggestedQuestionsRef.current = results;
+ // We currently have a bug where the same question can be returned multiple times.
+ // This is a workaround to avoid that.
+ const questions = new Set();
+ const recommendedQuestions: ResultType[] = [];
+ const timeout = setTimeout(async () => {
if (cancelled) {
return;
}
- setResultsState({ results, fetching: false });
- });
+ const response = streamRecommendedQuestions(pointer.organizationId, pointer.siteId);
+ const stream = iterateStreamResponse(response);
+
+ for await (const { question } of stream) {
+ if (questions.has(question)) {
+ continue;
+ }
+
+ questions.add(question);
+ recommendedQuestions.push({
+ type: 'recommended-question',
+ id: question,
+ question,
+ });
+ cachedRecommendedQuestions = recommendedQuestions;
+
+ if (!cancelled) {
+ setResultsState({ results: [...recommendedQuestions], fetching: false });
+ }
+ }
+ }, 100);
return () => {
cancelled = true;
+ clearTimeout(timeout);
};
} else {
setResultsState((prev) => ({ results: prev.results, fetching: true }));
@@ -142,7 +155,7 @@ export const SearchResults = React.forwardRef(function SearchResults(
clearTimeout(timeout);
};
}
- }, [query, global, pointer, spaceId, revisionId, withAsk, trackEvent]);
+ }, [query, global, pointer, revisionId, withAsk, trackEvent]);
const results: ResultType[] = React.useMemo(() => {
if (!withAsk) {
diff --git a/packages/gitbook/src/components/Search/server-actions.tsx b/packages/gitbook/src/components/Search/server-actions.tsx
index c82d48bf8..f8e9293e4 100644
--- a/packages/gitbook/src/components/Search/server-actions.tsx
+++ b/packages/gitbook/src/components/Search/server-actions.tsx
@@ -227,12 +227,20 @@ export const streamAskQuestion = streamResponse(async function* (
});
/**
- * List suggested questions for a space.
+ * Stream a list of suggested questions for the site.
*/
-export async function getRecommendedQuestions(spaceId: string): Promise {
- const data = await api.getRecommendedQuestionsInSpace(spaceId);
- return data.questions;
-}
+export const streamRecommendedQuestions = streamResponse(async function* (
+ organizationId: string,
+ siteId: string,
+) {
+ const apiCtx = await api.api();
+ const stream = apiCtx.client.orgs.streamRecommendedQuestionsInSite(organizationId, siteId);
+
+ for await (const chunk of stream) {
+ console.log('got question', chunk);
+ yield chunk;
+ }
+});
async function transformAnswer(
answer: SearchAIAnswer,
diff --git a/packages/gitbook/src/components/SpaceLayout/SpaceLayout.tsx b/packages/gitbook/src/components/SpaceLayout/SpaceLayout.tsx
index 8c2027665..a214c22d8 100644
--- a/packages/gitbook/src/components/SpaceLayout/SpaceLayout.tsx
+++ b/packages/gitbook/src/components/SpaceLayout/SpaceLayout.tsx
@@ -183,7 +183,6 @@ export async function SpaceLayout(props: {
getAPICacheTag({ tag: 'space', space: spaceId }),
- get: async (spaceId: string, options: CacheFunctionOptions) => {
- const apiCtx = await api();
- const response = await apiCtx.client.spaces.getRecommendedQuestionsInSpace(spaceId, {
- ...noCacheFetchOptions,
- signal: options.signal,
- });
- return cacheResponse(response);
- },
-});
-
/**
* Render an integration contentkit UI
*/
diff --git a/packages/react-contentkit/package.json b/packages/react-contentkit/package.json
index e47c9c241..4a36a35ea 100644
--- a/packages/react-contentkit/package.json
+++ b/packages/react-contentkit/package.json
@@ -10,7 +10,7 @@
},
"dependencies": {
"classnames": "^2.5.1",
- "@gitbook/api": "^0.89.0",
+ "@gitbook/api": "^0.90.0",
"assert-never": "^1.2.1"
},
"peerDependencies": {