diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 25c016b..82d958d 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -29,6 +29,9 @@ jobs: run: | bun install bun run build + - name: Sync search index + run: | + bun run sync-search - name: Add CNAME run: | echo "mx-space.js.org" > out/CNAME diff --git a/.gitignore b/.gitignore index 55a12ae..71092db 100644 --- a/.gitignore +++ b/.gitignore @@ -25,4 +25,5 @@ yarn-error.log* # others .env*.local .vercel -next-env.d.ts \ No newline at end of file +next-env.d.ts +.env diff --git a/app/api/search/route.ts b/app/api/search/route.ts index bfcd688..fab05ba 100644 --- a/app/api/search/route.ts +++ b/app/api/search/route.ts @@ -1,7 +1,41 @@ -import { source } from '@/lib/source'; -import { createFromSource } from 'fumadocs-core/search/server'; +import { sync } from 'fumadocs-core/search/orama-cloud'; +import * as fs from 'node:fs/promises'; +import { CloudManager } from '@oramacloud/client'; -// it should be cached forever -export const revalidate = false; +export const dynamic = 'force-static'; -export const { staticGET: GET } = createFromSource(source); \ No newline at end of file +const updateSearchIndexes = async (): Promise => { + const apiKey = process.env.ORAMA_PRIVATE_API_KEY; + const indexId = 'k2hnq39jnt7u8l41bfv0ezhd'; + + if (!apiKey) { + console.log('未找到 Orama 私钥, 跳过索引更新'); + return; + } + + try { + const content = await fs.readFile('.next/server/app/static.json.body'); + const records = JSON.parse(content.toString()); + + const manager = new CloudManager({ api_key: apiKey }); + + await sync(manager, { + index: indexId, + documents: records, + }); + + console.log(`搜索索引更新完成: ${records.length} 条记录`); + } catch (error) { + console.error('更新搜索索引时发生错误:', error); + } +} + +void updateSearchIndexes(); + +export async function GET(request: Request) { + // ... GET处理逻辑 ... +} + +export async function POST(request: Request) { + // ... POST处理逻辑 ... +} \ No newline at end of file diff --git a/app/components/provider.tsx b/app/components/provider.tsx new file mode 100644 index 0000000..8276ec2 --- /dev/null +++ b/app/components/provider.tsx @@ -0,0 +1,18 @@ +'use client'; +import { RootProvider } from 'fumadocs-ui/provider'; +import dynamic from 'next/dynamic'; +import type { ReactNode } from 'react'; + +const SearchDialog = dynamic(() => import('@/app/components/search')); // lazy load + +export function Provider({ children }: { children: ReactNode }) { + return ( + + {children} + + ); +} \ No newline at end of file diff --git a/app/components/search.tsx b/app/components/search.tsx new file mode 100644 index 0000000..dcfdb4b --- /dev/null +++ b/app/components/search.tsx @@ -0,0 +1,14 @@ +'use client'; + +import { OramaClient } from '@oramacloud/client'; +import type { SharedProps } from 'fumadocs-ui/components/dialog/search'; +import SearchDialog from 'fumadocs-ui/components/dialog/search-orama'; + +const client = new OramaClient({ + endpoint: 'https://cloud.orama.run/v1/indexes/mxspace-no50lj', + api_key: 'HHIpRwosmxFfAs7l2gsJv5m5A3ew2PRB', +}); + +export default function CustomSearchDialog(props: SharedProps) { + return ; +} \ No newline at end of file diff --git a/app/layout.tsx b/app/layout.tsx index dbc4b48..61e9911 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,5 +1,5 @@ import './global.css'; -import { RootProvider } from 'fumadocs-ui/provider'; +import { Provider } from './components/provider'; import { Inter } from 'next/font/google'; import type { ReactNode } from 'react'; @@ -10,16 +10,16 @@ const inter = Inter({ export default function Layout({ children }: { children: ReactNode }) { return ( - - + {children} - + ); diff --git a/app/static.json/route.ts b/app/static.json/route.ts new file mode 100644 index 0000000..f7d450f --- /dev/null +++ b/app/static.json/route.ts @@ -0,0 +1,25 @@ +import { NextResponse } from 'next/server'; +import { source } from '@/lib/source'; +import type { OramaDocument } from 'fumadocs-core/search/orama-cloud'; + +export const revalidate = false; + +export async function GET(): Promise { + const pages = source.getPages(); + const results = await Promise.all( + pages.map(async (page) => { + const { structuredData } = await page.data; + + return { + id: page.url, + structured: structuredData, + tag: page.slugs[0], + url: page.url, + title: page.data.title, + description: page.data.description, + } satisfies OramaDocument; + }), + ); + + return NextResponse.json(results); +} \ No newline at end of file diff --git a/bun.lockb b/bun.lockb index 6dd60f8..ff06ab2 100644 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/components/ToGitHub.tsx b/components/ToGitHub.tsx deleted file mode 100644 index 6de412b..0000000 --- a/components/ToGitHub.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import { Github } from 'lucide-react'; -import React from 'react'; - -export const ToGitHub = ({ repo }: { repo: string }) => { - return ( - - - GitHub - - ); -}; \ No newline at end of file diff --git a/package.json b/package.json index 0d09f75..4db5e41 100644 --- a/package.json +++ b/package.json @@ -6,12 +6,16 @@ "build": "next build", "dev": "next dev --turbo", "start": "next start", - "postinstall": "fumadocs-mdx" + "postinstall": "fumadocs-mdx", + "sync-search": "bun run sync-index.mjs" }, "dependencies": { "@orama/orama": "^3.0.1", + "@orama/react-components": "^0.1.11", "@orama/tokenizers": "^3.0.1", + "@oramacloud/client": "^1.3.19", "copy-to-clipboard": "^3.3.3", + "dotenv": "^16.4.5", "framer-motion": "^11.11.11", "fumadocs-core": "14.2.0", "fumadocs-mdx": "11.1.1", diff --git a/sync-index.mjs b/sync-index.mjs new file mode 100644 index 0000000..47798de --- /dev/null +++ b/sync-index.mjs @@ -0,0 +1,47 @@ +import { sync } from 'fumadocs-core/search/orama-cloud'; +import * as fs from 'node:fs/promises'; +import { CloudManager } from '@oramacloud/client'; +import * as path from 'path'; +import * as dotenv from 'dotenv'; + +// 加载 .env 文件 +dotenv.config(); + +export async function updateSearchIndexes() { + const apiKey = process.env.ORAMA_PRIVATE_API_KEY; + const indexId = 'k2hnq39jnt7u8l41bfv0ezhd'; // 你的索引 ID + + if (!apiKey) { + console.log('未找到 Orama 私钥, 跳过索引更新'); + return; + } + + try { + // 使用绝对路径 + const staticJsonPath = path.join(process.cwd(), '.next/server/app/static.json.body'); + + try { + await fs.access(staticJsonPath); + } catch { + console.error('static.json.body 文件不存在,请先运行 next build'); + process.exit(1); + } + + const content = await fs.readFile(staticJsonPath); + const records = JSON.parse(content.toString()); + + const manager = new CloudManager({ api_key: apiKey }); + + await sync(manager, { + index: indexId, + documents: records, + }); + + console.log(`搜索索引更新完成: ${records.length} 条记录`); + } catch (error) { + console.error('更新搜索索引时发生错误:', error); + process.exit(1); + } +} + +void updateSearchIndexes(); \ No newline at end of file