Files
supabase/apps/docs/resources/globalSearch/globalSearchModel.ts
T
Charis e46ab9c1a2 refactor: reading markdown docs files (#37774)
* refactor: reading markdown docs files

Refactor how Markdown docs files are read:
- Reuses the same logic across search index generation & page generation
- Improves the indexed content for search:
  - Stops removing MDX components, which often contain useful
    information like Admonitions
  - Denormalizes Partials and CodeSamples for more complete content

This is a prerequisite step for implementing the "Copy docs as Markdown"
functionality.

Only touches regular guides for now, not federated ones.

* fix: tailwind build error (#37728)

We changed to default to ESM imports a while ago, which means local
builds are now breaking because the Tailwind uses a require. Changed to
CJS for Tailwind config file. (I have no idea how this has been working
on Vercel all this time.)

* style: prettier
2025-08-13 11:37:14 -04:00

126 lines
3.9 KiB
TypeScript

import { type RootQueryTypeSearchDocsArgs } from '~/__generated__/graphql'
import { convertPostgrestToApiError, type ApiErrorGeneric } from '~/app/api/utils'
import { Result } from '~/features/helpers.fn'
import { openAI } from '~/lib/openAi'
import { supabase, type DatabaseCorrected } from '~/lib/supabase'
import { GuideModel } from '../guide/guideModel'
import {
DB_METADATA_TAG_PLATFORM_CLI,
ReferenceCLICommandModel,
} from '../reference/referenceCLIModel'
import { ReferenceManagementApiModel } from '../reference/referenceManagementApiModel'
import { ReferenceSDKFunctionModel, SDKLanguageValues } from '../reference/referenceSDKModel'
import { TroubleshootingModel } from '../troubleshooting/troubleshootingModel'
import { SearchResultInterface } from './globalSearchInterface'
export abstract class SearchResultModel {
static async search(
args: RootQueryTypeSearchDocsArgs,
requestedFields: Array<string>
): Promise<Result<SearchResultModel[], ApiErrorGeneric>> {
const query = args.query.trim()
const includeFullContent = requestedFields.includes('content')
const embeddingResult = await openAI().createContentEmbedding(query)
return embeddingResult.flatMapAsync(async ({ embedding }) => {
const matchResult = new Result(
await supabase().rpc('search_content', {
embedding,
include_full_content: includeFullContent,
max_result: args.limit ?? undefined,
})
)
.map((matches) =>
matches
.map(createModelFromMatch)
.filter((item): item is SearchResultInterface => item !== null)
)
.mapError(convertPostgrestToApiError)
return matchResult
})
}
static async searchHybrid(
args: RootQueryTypeSearchDocsArgs,
requestedFields: Array<string>
): Promise<Result<SearchResultModel[], ApiErrorGeneric>> {
const query = args.query.trim()
const includeFullContent = requestedFields.includes('content')
const embeddingResult = await openAI().createContentEmbedding(query)
return embeddingResult.flatMapAsync(async ({ embedding }) => {
const matchResult = new Result(
await supabase().rpc('search_content_hybrid', {
query_text: query,
query_embedding: embedding,
include_full_content: includeFullContent,
max_result: args.limit ?? 30,
})
)
.map((matches) =>
matches
.map(createModelFromMatch)
.filter((item): item is SearchResultInterface => item !== null)
)
.mapError(convertPostgrestToApiError)
return matchResult
})
}
}
function createModelFromMatch({
type,
page_title,
href,
content,
metadata,
subsections,
}: DatabaseCorrected['public']['Functions']['search_content']['Returns'][number]): SearchResultInterface | null {
switch (type) {
case 'markdown':
return new GuideModel({
title: page_title,
href,
content,
subsections,
})
case 'reference':
const { language } = metadata
if (language && SDKLanguageValues.includes(language)) {
return new ReferenceSDKFunctionModel({
title: page_title,
href,
content,
language,
methodName: metadata.methodName,
})
} else if (metadata.platform === DB_METADATA_TAG_PLATFORM_CLI) {
return new ReferenceCLICommandModel({
title: page_title,
href,
content,
subsections,
})
// TODO [Charis 2025-06-09] replace with less hacky check
} else if (metadata.subtitle?.startsWith('Management API Reference')) {
return new ReferenceManagementApiModel({
title: page_title,
href,
content,
})
} else {
return null
}
case 'github-discussions':
return new TroubleshootingModel({
title: page_title,
href,
content,
})
default:
return null
}
}