Skip to content

Commit 1f36159

Browse files
committed
feat(helpdesk): support finding article sections via autocomplete
1 parent bf540da commit 1f36159

3 files changed

Lines changed: 63 additions & 12 deletions

File tree

src/functions/autocomplete/helpdeskAutoComplete.ts

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import {
99
DISCORD_HELPDESK_DEV_BASE,
1010
DJS_QUERY_SEPARATOR,
1111
} from '../../util/constants.js';
12+
import { findRelevantDocsSectionFuzzy, parseGithubDocsSections } from '../../util/discordDocs.js';
13+
import { helpdeskTurndownService } from '../../util/helpdeskturndown.js';
1214
import { prepareHeader } from '../../util/respond.js';
1315
import { truncate } from '../../util/truncate.js';
1416

@@ -19,13 +21,28 @@ export function zendeskAutocompleteValue(article: ZendeskSearchResult, query: st
1921
return `${prefix}${query.slice(0, AUTOCOMPLETE_MAX_NAME_LENGTH - prefix.length)}`;
2022
}
2123

22-
function autocompleteMap(elements: ZendeskSearchResult[], query: string) {
23-
return elements.map((entry) => {
24-
return {
24+
function autocompleteMap(searchResults: ZendeskSearchResult[], query: string) {
25+
const res = [];
26+
27+
for (const entry of searchResults) {
28+
const mdParsed = helpdeskTurndownService.turndown(entry.body);
29+
const parsedSections = parseGithubDocsSections(mdParsed.split(/\n+/));
30+
const relevant = findRelevantDocsSectionFuzzy(parsedSections, query, false);
31+
32+
if (relevant?.linkAnchor) {
33+
res.push({
34+
name: truncate(`${entry.title} | ${relevant.headline}`, AUTOCOMPLETE_MAX_NAME_LENGTH),
35+
value: zendeskAutocompleteValue(entry, relevant.linkAnchor),
36+
});
37+
}
38+
39+
res.push({
2540
name: truncate(entry.title, AUTOCOMPLETE_MAX_NAME_LENGTH),
2641
value: zendeskAutocompleteValue(entry, query),
27-
};
28-
});
42+
});
43+
}
44+
45+
return res.slice(0, AUTOCOMPLETE_MAX_ITEMS);
2946
}
3047

3148
export async function zendeskQuery(query: string, developer = false) {

src/functions/helpdeskResponse.ts

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { hyperlink, inlineCode } from '@discordjs/builders';
1+
import { HeadingLevel, heading, hyperlink, inlineCode } from '@discordjs/builders';
22
import type { Response } from 'polka';
33
import type { ZendeskArticle, ZendeskEntity } from '../types/zendesk';
44
import {
@@ -15,6 +15,7 @@ import {
1515
import {
1616
SectionPartType,
1717
findRelevantDocsSectionFuzzy,
18+
findSectionFromAnchor,
1819
parseGithubDocsSections,
1920
sectionPartToText,
2021
} from '../util/discordDocs.js';
@@ -42,6 +43,15 @@ async function fetchHelpdeskArticle(id: number, autocompleteTimestamp: number, d
4243
);
4344
return hit;
4445
}
46+
47+
logger.debug(
48+
{
49+
id,
50+
autocompleteTimestamp,
51+
dev,
52+
},
53+
`cache hit but outdated for support article ${id}. refreshing...`,
54+
);
4555
}
4656

4757
const base = dev ? DISCORD_HELPDESK_DEV_ARTICLES_BASE : DISCORD_HELPDESK_ARTICLES_BASE;
@@ -61,12 +71,13 @@ export async function helpdeskResponse(
6171
ephemeral?: boolean,
6272
secondAttempt = false,
6373
) {
64-
const [articleId, timestampString, ...originalQuery] = query.split(DJS_QUERY_SEPARATOR);
74+
const [articleId, timestampString, ...originalQueryParts] = query.split(DJS_QUERY_SEPARATOR);
75+
const originalQuery = originalQueryParts.join(DJS_QUERY_SEPARATOR);
6576

6677
const numberId = Number.parseInt(articleId, 10);
6778
if (Number.isNaN(numberId)) {
6879
if (secondAttempt) {
69-
prepareErrorResponse(res, `No documentation found for ${inlineCode(originalQuery.join(DJS_QUERY_SEPARATOR))}.`);
80+
prepareErrorResponse(res, `No documentation found for ${inlineCode(originalQuery)}.`);
7081
return res;
7182
}
7283

@@ -87,10 +98,13 @@ export async function helpdeskResponse(
8798
const bodyParts = ['---', `title: ${article.title}`, '---', ...turnedDown.split(/\n+/)];
8899

89100
const parsedSections = parseGithubDocsSections(bodyParts);
90-
const relevantSection = findRelevantDocsSectionFuzzy(parsedSections, originalQuery.join(DJS_QUERY_SEPARATOR), true);
101+
102+
const relevantSection = originalQuery.startsWith('#h')
103+
? findSectionFromAnchor(parsedSections, originalQuery)
104+
: findRelevantDocsSectionFuzzy(parsedSections, originalQuery, true);
91105

92106
if (!relevantSection) {
93-
prepareErrorResponse(res, `No documentation found for ${inlineCode(query)}.`);
107+
prepareErrorResponse(res, `No documentation found for ${inlineCode(originalQuery)}.`);
94108
return res;
95109
}
96110

src/util/discordDocs.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -398,8 +398,20 @@ export function sectionPartToText(part: SectionPart) {
398398
}
399399

400400
if (part.type === SectionPartType.Admonition) {
401-
const prefixed = part.lines.map((line) => quote(line));
402-
return prefixed.join('\n');
401+
const prefixedLines = [];
402+
let first = true;
403+
for (const line of part.lines) {
404+
if (first) {
405+
prefixedLines.push(quote(`${inlineCode(admonitionEmoji(part.admonitionType))} ${line}`));
406+
407+
first = false;
408+
continue;
409+
}
410+
411+
prefixedLines.push(quote(line));
412+
}
413+
414+
return prefixedLines.join('\n');
403415
}
404416

405417
if (part.type === SectionPartType.Code) {
@@ -434,6 +446,14 @@ export function findRelevantDocsSection(sections: Section[], query: string, defa
434446
}
435447
}
436448

449+
export function findSectionFromAnchor(sections: Section[], anchor: string) {
450+
for (const section of sections) {
451+
if (anchor === section.linkAnchor) {
452+
return section;
453+
}
454+
}
455+
}
456+
437457
export function findRelevantDocsSectionFuzzy(sections: Section[], query: string, defaultFirst = false) {
438458
const lowerQuery = query.trim().toLowerCase();
439459
const headingMatches = [];

0 commit comments

Comments
 (0)