mirror of
https://github.com/supabase/supabase.git
synced 2026-07-02 10:44:33 +08:00
Add a top-level field to search docs globally. Right now this only
returns Markdown guides (not references, GitHub discussions, or partner
pages.
The full GraphQL schema at this point:
```
schema {
query: RootQueryType
}
type RootQueryType {
"""Get the GraphQL schema for this endpoint"""
schema: String!
"""Search the Supabase docs for content matching a query string"""
searchDocs(query: String!, limit: Int): SearchResultCollection
}
"""A collection of search results containing content from Supabase docs"""
type SearchResultCollection {
"""A list of edges containing nodes in this collection"""
edges: [SearchResultEdge!]!
"""The nodes in this collection, directly accessible"""
nodes: [SearchResult!]!
"""The total count of items available in this collection"""
totalCount: Int!
}
"""An edge in a collection of SearchResults"""
type SearchResultEdge {
"""The SearchResult at the end of the edge"""
node: SearchResult!
}
"""Document that matches a search query"""
interface SearchResult {
"""The title of the matching result"""
title: String
"""The URL of the matching result"""
href: String
"""The full content of the matching result"""
content: String
}
```
Towards DOCS-214
68 lines
1.8 KiB
TypeScript
68 lines
1.8 KiB
TypeScript
import OpenAI from 'openai'
|
|
import 'server-only'
|
|
import {
|
|
convertUnknownToApiError,
|
|
InvalidRequestError,
|
|
type ApiError,
|
|
type ApiErrorGeneric,
|
|
} from '~/app/api/utils'
|
|
import { Result } from '~/features/helpers.fn'
|
|
|
|
type Embedding = Array<number>
|
|
|
|
interface ModerationFlaggedDetails {
|
|
flagged: boolean
|
|
categories: OpenAI.Moderations.Moderation.Categories
|
|
}
|
|
|
|
export interface OpenAIClientInterface {
|
|
createContentEmbedding(text: string): Promise<Result<Embedding, ApiErrorGeneric>>
|
|
}
|
|
|
|
let openAIClient: OpenAIClientInterface | null
|
|
|
|
class OpenAIClient implements OpenAIClientInterface {
|
|
static CONTENT_EMBEDDING_MODEL = 'text-embedding-ada-002'
|
|
|
|
constructor(private client: OpenAI) {}
|
|
|
|
async createContentEmbedding(text: string): Promise<Result<Embedding, ApiErrorGeneric>> {
|
|
return await Result.tryCatchFlat(
|
|
this.createContentEmbeddingImpl.bind(this),
|
|
convertUnknownToApiError,
|
|
text
|
|
)
|
|
}
|
|
|
|
private async createContentEmbeddingImpl(
|
|
text: string
|
|
): Promise<Result<Embedding, ApiError<ModerationFlaggedDetails>>> {
|
|
const query = text.trim()
|
|
|
|
const moderationResponse = await this.client.moderations.create({ input: query })
|
|
const [result] = moderationResponse.results
|
|
if (result.flagged) {
|
|
return Result.error(
|
|
new InvalidRequestError('Content flagged as inappropriate', undefined, {
|
|
flagged: true,
|
|
categories: result.categories,
|
|
})
|
|
)
|
|
}
|
|
|
|
const embeddingsResponse = await this.client.embeddings.create({
|
|
model: OpenAIClient.CONTENT_EMBEDDING_MODEL,
|
|
input: query,
|
|
})
|
|
const [{ embedding: queryEmbedding }] = embeddingsResponse.data
|
|
return Result.ok(queryEmbedding)
|
|
}
|
|
}
|
|
|
|
export function openAI(): OpenAIClientInterface {
|
|
if (!openAIClient) {
|
|
openAIClient = new OpenAIClient(new OpenAI())
|
|
}
|
|
return openAIClient
|
|
}
|