From 9eca010f945b2d2232df48a44923c98fbe91c271 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Samy=20Pess=C3=A9?= Date: Fri, 31 Jan 2025 23:13:40 +0100 Subject: [PATCH] Improve display of recommended questions using streaming (#2800) --- .changeset/pretty-flies-jump.md | 5 ++ .github/workflows/ci.yaml | 4 -- bun.lock | 9 +-- packages/gitbook-v2/package.json | 2 +- packages/gitbook/package.json | 2 +- .../middleware/(space)/~gitbook/pdf/page.tsx | 2 +- .../src/components/Search/SearchModal.tsx | 3 - .../src/components/Search/SearchResults.tsx | 65 +++++++++++-------- .../src/components/Search/server-actions.tsx | 18 +++-- .../components/SpaceLayout/SpaceLayout.tsx | 1 - packages/gitbook/src/lib/api.ts | 16 ----- packages/react-contentkit/package.json | 2 +- 12 files changed, 66 insertions(+), 63 deletions(-) create mode 100644 .changeset/pretty-flies-jump.md 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": {