Skip to content

Commit 53de5b1

Browse files
authored
Fix URL resolution in Site Sections for Ask (#2827)
1 parent a025118 commit 53de5b1

File tree

3 files changed

+86
-50
lines changed

3 files changed

+86
-50
lines changed

.changeset/curvy-pets-brush.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'gitbook': patch
3+
---
4+
5+
Fix site section URL resolution in Ask AI sources

packages/gitbook/src/components/Search/SearchAskAnswer.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ export function SearchAskAnswer(props: { pointer: SiteContentPointer; query: str
5252
query,
5353
});
5454

55-
const response = streamAskQuestion(organizationId, siteId, siteSpaceId ?? null, query);
55+
const response = streamAskQuestion({ pointer, question: query });
5656
const stream = iterateStreamResponse(response);
5757

5858
// When we pass in "ask" mode, the query could still be updated by the client

packages/gitbook/src/components/Search/server-actions.tsx

Lines changed: 80 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
'use server';
22

3-
import { RevisionPage, SearchAIAnswer, SearchPageResult, SiteSpace, Space } from '@gitbook/api';
3+
import {
4+
RevisionPage,
5+
SearchAIAnswer,
6+
SearchPageResult,
7+
SiteSpace,
8+
SiteStructure,
9+
Space,
10+
} from '@gitbook/api';
411
import * as React from 'react';
512
import { assert } from 'ts-essentials';
613

@@ -75,22 +82,7 @@ async function searchSiteContent(args: {
7582
]);
7683
const siteStructure = siteData?.structure;
7784

78-
const siteSpaces = siteStructure
79-
? siteStructure.type === 'siteSpaces'
80-
? siteStructure.structure
81-
: getSiteStructureSections(siteStructure).reduce<SiteSpace[]>((prev, section) => {
82-
const sectionSiteSpaces = section.siteSpaces.map((siteSpace) => ({
83-
...siteSpace,
84-
space: {
85-
...siteSpace.space,
86-
title: section.title || siteSpace.space.title,
87-
},
88-
}));
89-
90-
prev.push(...sectionSiteSpaces);
91-
return prev;
92-
}, [])
93-
: null;
85+
const siteSpaces = siteStructure ? extractSiteStructureSiteSpaces(siteStructure) : null;
9486

9587
if (siteSpaces) {
9688
// We are searching all of this Site's content
@@ -159,13 +151,19 @@ export async function searchSiteSpaceContent(
159151
/**
160152
* Server action to ask a question in a space.
161153
*/
162-
export const streamAskQuestion = streamResponse(async function* (
163-
organizationId: string,
164-
siteId: string,
165-
siteSpaceId: string | null,
166-
question: string,
167-
) {
168-
const apiCtx = await api.api();
154+
export const streamAskQuestion = streamResponse(async function* ({
155+
pointer,
156+
question,
157+
}: {
158+
pointer: api.SiteContentPointer;
159+
question: string;
160+
}) {
161+
const { organizationId, siteId, siteSpaceId } = pointer;
162+
const [apiCtx, siteData] = await Promise.all([api.api(), api.getSiteData(pointer)]);
163+
const siteSpaces = siteData?.structure
164+
? extractSiteStructureSiteSpaces(siteData.structure)
165+
: null;
166+
169167
const stream = apiCtx.client.orgs.streamAskInSite(
170168
organizationId,
171169
siteId,
@@ -222,7 +220,7 @@ export const streamAskQuestion = streamResponse(async function* (
222220
return map;
223221
}, new Map<string, RevisionPage[]>());
224222
});
225-
yield await transformAnswer(chunk.answer, pages);
223+
yield await transformAnswer({ answer: chunk.answer, spacePages: pages, siteSpaces });
226224
}
227225
});
228226

@@ -237,15 +235,19 @@ export const streamRecommendedQuestions = streamResponse(async function* (
237235
const stream = apiCtx.client.orgs.streamRecommendedQuestionsInSite(organizationId, siteId);
238236

239237
for await (const chunk of stream) {
240-
console.log('got question', chunk);
241238
yield chunk;
242239
}
243240
});
244241

245-
async function transformAnswer(
246-
answer: SearchAIAnswer,
247-
spacePages: Map<string, RevisionPage[]>,
248-
): Promise<AskAnswerResult> {
242+
async function transformAnswer({
243+
answer,
244+
spacePages,
245+
siteSpaces,
246+
}: {
247+
answer: SearchAIAnswer;
248+
spacePages: Map<string, RevisionPage[]>;
249+
siteSpaces: SiteSpace[] | null;
250+
}): Promise<AskAnswerResult> {
249251
const sources = (
250252
await Promise.all(
251253
answer.sources.map(async (source) => {
@@ -264,10 +266,19 @@ async function transformAnswer(
264266
return null;
265267
}
266268

269+
// Find the siteSpace in case it is nested in a site section so we can resolve the URL appropriately
270+
const spaceURL = siteSpaces?.find(
271+
(siteSpace) => siteSpace.space.id === source.space,
272+
)?.urls.published;
273+
274+
const href = spaceURL
275+
? await getURLWithSections(page.page.path, spaceURL)
276+
: await getPageHref(pages, page.page);
277+
267278
return {
268279
id: source.page,
269280
title: page.page.title,
270-
href: await getPageHref(pages, page.page),
281+
href,
271282
};
272283
}),
273284
)
@@ -299,28 +310,12 @@ async function transformSectionsAndPage(args: {
299310
}): Promise<[ComputedPageResult, ComputedSectionResult[]]> {
300311
const { item, space, spaceURL } = args;
301312

302-
// Resolve a relative path to an absolute URL
303-
// if the search result is relative to another space, we use the space URL
304-
const getURL = async (path: string, spaceURL?: string) => {
305-
if (spaceURL) {
306-
if (!spaceURL.endsWith('/')) {
307-
spaceURL += '/';
308-
}
309-
if (path.startsWith('/')) {
310-
path = path.slice(1);
311-
}
312-
return spaceURL + path;
313-
} else {
314-
return getAbsoluteHref(path);
315-
}
316-
};
317-
318313
const sections = await Promise.all(
319314
item.sections?.map<Promise<ComputedSectionResult>>(async (section) => ({
320315
type: 'section',
321316
id: item.id + '/' + section.id,
322317
title: section.title,
323-
href: await getURL(section.path, spaceURL),
318+
href: await getURLWithSections(section.path, spaceURL),
324319
body: section.body,
325320
})) ?? [],
326321
);
@@ -329,7 +324,7 @@ async function transformSectionsAndPage(args: {
329324
type: 'page',
330325
id: item.id,
331326
title: item.title,
332-
href: await getURL(item.path, spaceURL),
327+
href: await getURLWithSections(item.path, spaceURL),
333328
spaceTitle: space?.title,
334329
};
335330

@@ -355,3 +350,39 @@ async function transformPageResult(item: SearchPageResult, space?: Space) {
355350

356351
return [page, ...sections];
357352
}
353+
354+
// Resolve a relative path to an absolute URL
355+
// if the search result is relative to another space, we use the space URL
356+
async function getURLWithSections(path: string, spaceURL?: string) {
357+
if (spaceURL) {
358+
if (!spaceURL.endsWith('/')) {
359+
spaceURL += '/';
360+
}
361+
if (path.startsWith('/')) {
362+
path = path.slice(1);
363+
}
364+
return spaceURL + path;
365+
} else {
366+
return getAbsoluteHref(path);
367+
}
368+
}
369+
370+
/*
371+
* Gets all site spaces, in a site structure and overrides the title
372+
*/
373+
function extractSiteStructureSiteSpaces(siteStructure: SiteStructure) {
374+
return siteStructure.type === 'siteSpaces'
375+
? siteStructure.structure
376+
: getSiteStructureSections(siteStructure).reduce<SiteSpace[]>((prev, section) => {
377+
const sectionSiteSpaces = section.siteSpaces.map((siteSpace) => ({
378+
...siteSpace,
379+
space: {
380+
...siteSpace.space,
381+
title: section.title || siteSpace.space.title,
382+
},
383+
}));
384+
385+
prev.push(...sectionSiteSpaces);
386+
return prev;
387+
}, []);
388+
}

0 commit comments

Comments
 (0)