mirror of
https://github.com/hanzo-docs/docs.git
synced 2026-10-25 04:20:02 +00:00
Core: Support mode option in search server
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'fumadocs-core': patch
|
||||
---
|
||||
|
||||
Support `mode` option in search server
|
||||
@@ -30,6 +30,7 @@ import { Installation } from '@/components/preview/installation';
|
||||
import { Customisation } from '@/components/preview/customisation';
|
||||
import { DocsPage } from 'fumadocs-ui/page';
|
||||
import { NotFound } from '@/components/not-found';
|
||||
import { getSuggestions } from '@/app/docs/[...slug]/suggestions';
|
||||
|
||||
function PreviewRenderer({ preview }: { preview: string }): ReactNode {
|
||||
if (preview && preview in Preview) {
|
||||
@@ -48,7 +49,10 @@ export default async function Page(props: PageProps<'/docs/[...slug]'>) {
|
||||
const params = await props.params;
|
||||
const page = source.getPage(params.slug);
|
||||
|
||||
if (!page) return <NotFound slug={params.slug} />;
|
||||
if (!page)
|
||||
return (
|
||||
<NotFound getSuggestions={() => getSuggestions(params.slug.join(' '))} />
|
||||
);
|
||||
|
||||
const preview = page.data.preview;
|
||||
const { body: Mdx, toc, lastModified } = page.data;
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { OramaClient } from '@oramacloud/client';
|
||||
import type { Suggestion } from '@/components/not-found';
|
||||
|
||||
const client = new OramaClient({
|
||||
endpoint: 'https://cloud.orama.run/v1/indexes/docs-fk97oe',
|
||||
api_key: 'oPZjdlFbq5BpR54bV5Vj57RYt83Xosk7',
|
||||
});
|
||||
|
||||
export async function getSuggestions(pathname: string): Promise<Suggestion[]> {
|
||||
const results = await client.search({
|
||||
term: pathname,
|
||||
mode: 'vector',
|
||||
groupBy: {
|
||||
properties: ['url'],
|
||||
maxResult: 1,
|
||||
},
|
||||
});
|
||||
|
||||
if (!results?.groups) return [];
|
||||
|
||||
return results.groups.map((group) => {
|
||||
const doc = group.result[0];
|
||||
|
||||
return {
|
||||
id: doc.id,
|
||||
href: doc.document.url,
|
||||
title: doc.document.title,
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -1,43 +1,44 @@
|
||||
import { OramaClient } from '@oramacloud/client';
|
||||
import Link from 'fumadocs-core/link';
|
||||
import { Suspense } from 'react';
|
||||
import { type ReactNode, Suspense } from 'react';
|
||||
import { cn } from '@/lib/cn';
|
||||
import { buttonVariants } from 'fumadocs-ui/components/ui/button';
|
||||
|
||||
const client = new OramaClient({
|
||||
endpoint: 'https://cloud.orama.run/v1/indexes/docs-fk97oe',
|
||||
api_key: 'oPZjdlFbq5BpR54bV5Vj57RYt83Xosk7',
|
||||
});
|
||||
export interface Suggestion {
|
||||
id: string;
|
||||
href: string;
|
||||
title: ReactNode;
|
||||
}
|
||||
|
||||
export function NotFound({ slug }: { slug: string[] }) {
|
||||
export interface NotFoundProps {
|
||||
getSuggestions: () => Promise<Suggestion[]>;
|
||||
}
|
||||
|
||||
export function NotFound(props: NotFoundProps) {
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center text-center gap-4 flex-1 p-8 me-(--fd-toc-width)">
|
||||
<h1 className="text-4xl font-bold font-mono">Not Found</h1>
|
||||
<div className="w-full border border-fd-foreground/50 border-dashed p-4 max-w-[400px]">
|
||||
<Suspense
|
||||
fallback={<p className="font-medium">Finding Alternatives...</p>}
|
||||
fallback={
|
||||
<p className="text-sm text-fd-muted-foreground">
|
||||
Finding Alternatives...
|
||||
</p>
|
||||
}
|
||||
>
|
||||
<Alternative term={slug.join(' ')} />
|
||||
<Alternative {...props} />
|
||||
</Suspense>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
async function Alternative({ term }: { term: string }) {
|
||||
const result = await client.search({
|
||||
term,
|
||||
mode: 'vector',
|
||||
groupBy: {
|
||||
properties: ['url'],
|
||||
maxResult: 1,
|
||||
},
|
||||
});
|
||||
async function Alternative({ getSuggestions }: NotFoundProps) {
|
||||
const suggestions = await getSuggestions();
|
||||
|
||||
if (!result.groups || result.groups.length === 0) {
|
||||
if (suggestions.length === 0) {
|
||||
return (
|
||||
<div>
|
||||
<p className="font-medium mb-2">No Alternative Found</p>
|
||||
<p className="mb-2">No Alternative Found</p>
|
||||
<Link href="/" className={cn(buttonVariants({ variant: 'secondary' }))}>
|
||||
Return to Home
|
||||
</Link>
|
||||
@@ -47,23 +48,19 @@ async function Alternative({ term }: { term: string }) {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h2 className="font-medium mb-4">Maybe you are looking for</h2>
|
||||
<h2 className="mb-4">Maybe you are looking for</h2>
|
||||
|
||||
<div className="flex flex-col gap-2">
|
||||
{result.groups.map((group) => {
|
||||
const doc = group.result[0];
|
||||
|
||||
return (
|
||||
<Link
|
||||
key={doc.id}
|
||||
href={doc.document.url}
|
||||
className="block text-sm p-3 rounded-lg border bg-fd-card text-fd-card-foreground shadow-md transition-colors hover:bg-fd-accent hover:text-fd-accent-foreground"
|
||||
>
|
||||
<p className="font-medium">{doc.document.title}</p>
|
||||
<p className="text-fd-muted-foreground">{doc.document.url}</p>
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
{suggestions.map((doc) => (
|
||||
<Link
|
||||
key={doc.id}
|
||||
href={doc.href}
|
||||
className="block text-sm p-3 rounded-lg border bg-fd-card text-fd-card-foreground shadow-md transition-colors hover:bg-fd-accent hover:text-fd-accent-foreground"
|
||||
>
|
||||
<p className="font-medium">{doc.title}</p>
|
||||
<p className="text-fd-muted-foreground">{doc.href}</p>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -14,6 +14,7 @@ export const advancedSchema = {
|
||||
type: 'string',
|
||||
tags: 'enum[]',
|
||||
url: 'string',
|
||||
embeddings: 'vector[512]',
|
||||
} as const;
|
||||
|
||||
export async function createDB({
|
||||
|
||||
@@ -17,6 +17,7 @@ export function createEndpoint(server: SearchServer): SearchAPI {
|
||||
await search(query, {
|
||||
tag: url.searchParams.get('tag')?.split(',') ?? undefined,
|
||||
locale: url.searchParams.get('locale') ?? undefined,
|
||||
mode: url.searchParams.get('mode') === 'vector' ? 'vector' : 'full',
|
||||
}),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -11,14 +11,16 @@ export async function searchAdvanced(
|
||||
db: Orama<typeof advancedSchema>,
|
||||
query: string,
|
||||
tag: string | string[] = [],
|
||||
extraParams: Partial<
|
||||
SearchParams<Orama<typeof advancedSchema>, AdvancedDocument>
|
||||
> = {},
|
||||
{
|
||||
mode = 'fulltext',
|
||||
...override
|
||||
}: Partial<SearchParams<Orama<typeof advancedSchema>, AdvancedDocument>> = {},
|
||||
): Promise<SortedResult[]> {
|
||||
if (typeof tag === 'string') tag = [tag];
|
||||
|
||||
let params = {
|
||||
...extraParams,
|
||||
...override,
|
||||
mode,
|
||||
where: removeUndefined({
|
||||
tags:
|
||||
tag.length > 0
|
||||
@@ -26,12 +28,12 @@ export async function searchAdvanced(
|
||||
containsAll: tag,
|
||||
}
|
||||
: undefined,
|
||||
...extraParams.where,
|
||||
...override.where,
|
||||
}),
|
||||
groupBy: {
|
||||
properties: ['page_id'],
|
||||
maxResult: 8,
|
||||
...extraParams.groupBy,
|
||||
...override.groupBy,
|
||||
},
|
||||
} as SearchParams<typeof db, AdvancedDocument>;
|
||||
|
||||
@@ -39,7 +41,7 @@ export async function searchAdvanced(
|
||||
params = {
|
||||
...params,
|
||||
term: query,
|
||||
properties: ['content'],
|
||||
properties: mode === 'fulltext' ? ['content'] : ['content', 'embeddings'],
|
||||
} as SearchParams<typeof db, AdvancedDocument>;
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,11 @@ export type ExportedData =
|
||||
export interface SearchServer {
|
||||
search: (
|
||||
query: string,
|
||||
options?: { locale?: string; tag?: string | string[] },
|
||||
options?: {
|
||||
locale?: string;
|
||||
tag?: string | string[];
|
||||
mode?: 'vector' | 'full';
|
||||
},
|
||||
) => Promise<SortedResult[]>;
|
||||
|
||||
/**
|
||||
@@ -154,8 +158,23 @@ export function initAdvancedSearch(options: AdvancedOptions): SearchServer {
|
||||
},
|
||||
async search(query, searchOptions) {
|
||||
const db = await get;
|
||||
const mode = searchOptions?.mode;
|
||||
|
||||
return searchAdvanced(db, query, searchOptions?.tag, options.search);
|
||||
return searchAdvanced(db, query, searchOptions?.tag, {
|
||||
...options.search,
|
||||
mode: mode === 'vector' ? 'vector' : 'fulltext',
|
||||
}).catch((err) => {
|
||||
if (mode === 'vector') {
|
||||
throw new Error(
|
||||
'failed to search, make sure you have installed `@orama/plugin-embeddings` according to their docs.',
|
||||
{
|
||||
cause: err,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
throw err;
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user