mirror of
https://github.com/basketikun/infinite-canvas.git
synced 2026-07-24 06:54:06 +08:00
feat(docs): enhance documentation structure and add Docker support for standalone Next.js app
This commit is contained in:
@@ -2,6 +2,10 @@
|
|||||||
.idea
|
.idea
|
||||||
docs2
|
docs2
|
||||||
data
|
data
|
||||||
|
docs/.next
|
||||||
|
docs/.source
|
||||||
|
docs/node_modules
|
||||||
|
docs/out
|
||||||
web/.next
|
web/.next
|
||||||
web/node_modules
|
web/node_modules
|
||||||
web/out
|
web/out
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
name: Docs Docker image
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
tags: ["v*"]
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
packages: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
- id: meta
|
||||||
|
uses: docker/metadata-action@v5
|
||||||
|
with:
|
||||||
|
images: ghcr.io/${{ github.repository }}-docs
|
||||||
|
tags: |
|
||||||
|
type=raw,value=latest
|
||||||
|
type=ref,event=tag
|
||||||
|
type=sha,prefix=
|
||||||
|
|
||||||
|
- uses: docker/build-push-action@v6
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: ./docs/Dockerfile
|
||||||
|
push: true
|
||||||
|
tags: ${{ steps.meta.outputs.tags }}
|
||||||
|
labels: ${{ steps.meta.outputs.labels }}
|
||||||
|
cache-from: type=gha,scope=docs
|
||||||
|
cache-to: type=gha,mode=max,scope=docs
|
||||||
+5
-6
@@ -3,15 +3,14 @@
|
|||||||
## Unreleased
|
## Unreleased
|
||||||
|
|
||||||
+ [新增] 新增文档站点页面。
|
+ [新增] 新增文档站点页面。
|
||||||
|
+ [优化] 优化画布连线交互。
|
||||||
|
|
||||||
## v0.2.0 - 2026-06-01
|
## v0.2.0 - 2026-06-01
|
||||||
|
|
||||||
+ [新增] 支持通过火山方舟 Agent Plan 接入 Seedance 2.0 视频生成,后端会自动适配任务创建、轮询和结果回填接口。
|
+ [新增] 支持通过火山方舟AgentPlan接入。
|
||||||
+ [新增] 视频创作台和画布视频生成支持 Seedance 分辨率、比例、时长、生成声音、水印,以及参考图片、参考视频、参考音频输入。
|
+ [新增] 视频生成支持声音、水印及图片/视频/音频参考输入。
|
||||||
+ [新增] 画布新增音频节点,并新增参考素材上传与公开访问接口,配合 `PUBLIC_BASE_URL` 供火山方舟拉取本地参考素材。
|
+ [新增] 画布新增音频节点。
|
||||||
+ [优化] 管理后台模型渠道支持 Agent Plan Base URL 归一、Seedance 模型配置提示、渠道探测超时和已启用模型自动同步到公开可用模型。
|
+ [优化] 图片/视频素材支持 `图片1`编号注入提示词。
|
||||||
+ [优化] 图片/视频参考素材会按 `图片1`、`视频1`、`音频1` 编号注入提示词,图像请求尺寸和质量参数也会在请求前归一校验。
|
|
||||||
+ [修复] 修复 Docker 部署时 SQLite 相对路径可能写入错误目录的问题,并优化 AI 上游鉴权、限流和 Seedance 敏感参考视频错误提示。
|
|
||||||
|
|
||||||
## v0.1.1 - 2026-05-30
|
## v0.1.1 - 2026-05-30
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@ FROM oven/bun:1.3.13 AS web-build
|
|||||||
|
|
||||||
WORKDIR /app/web
|
WORKDIR /app/web
|
||||||
COPY web/package.json web/bun.lock ./
|
COPY web/package.json web/bun.lock ./
|
||||||
RUN --mount=type=cache,target=/root/.bun/install/cache bun install --frozen-lockfile --registry=https://registry.npmmirror.com --cache-dir=/root/.bun/install/cache
|
RUN --mount=type=cache,target=/root/.bun/install/cache bun install --frozen-lockfile --cache-dir=/root/.bun/install/cache
|
||||||
COPY VERSION /app/VERSION
|
COPY VERSION /app/VERSION
|
||||||
COPY CHANGELOG.md /app/CHANGELOG.md
|
COPY CHANGELOG.md /app/CHANGELOG.md
|
||||||
COPY web ./
|
COPY web ./
|
||||||
|
|||||||
@@ -101,6 +101,23 @@ https://infinite-canvas-cpco.onrender.com?apiKey={key}&baseUrl={address}
|
|||||||
- [系统配置数据结构](docs2/system-settings.md)
|
- [系统配置数据结构](docs2/system-settings.md)
|
||||||
- [接口响应约定](docs2/api-response.md)
|
- [接口响应约定](docs2/api-response.md)
|
||||||
|
|
||||||
|
## 赞助支持
|
||||||
|
|
||||||
|
<div align="center">
|
||||||
|
|
||||||
|
如果这个项目对你有帮助,欢迎通过爱发电赞助支持,你的每一份鼓励都是持续更新的动力!
|
||||||
|
|
||||||
|
<br>
|
||||||
|
|
||||||
|
<a href="https://ifdian.net/a/basketikun">
|
||||||
|
<img src="https://img.shields.io/badge/%E7%88%B1%E5%8F%91%E7%94%B5-%E8%B5%9E%E5%8A%A9%E4%BD%9C%E8%80%85-946ce6?style=for-the-badge&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0id2hpdGUiPjxwYXRoIGQ9Ik0xMiAyMS4zNWwtMS40NS0xLjMyQzUuNCAxNS4zNiAyIDEyLjI4IDIgOC41IDIgNS40MiA0LjQyIDMgNy41IDNjMS43NCAwIDMuNDEuODEgNC41IDIuMDlDMTMuMDkgMy44MSAxNC43NiAzIDE2LjUgMyAxOS41OCAzIDIyIDUuNDIgMjIgOC41YzAgMy43OC0zLjQgNi44Ni04LjU1IDExLjU0TDEyIDIxLjM1eiIvPjwvc3ZnPg==&logoColor=white" alt="爱发电赞助" />
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<br>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
## 社区支持
|
## 社区支持
|
||||||
|
|
||||||
学 AI,上 L 站:[LinuxDO](https://linux.do/)
|
学 AI,上 L 站:[LinuxDO](https://linux.do/)
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
node_modules
|
||||||
|
.next
|
||||||
|
out
|
||||||
|
.source
|
||||||
|
coverage
|
||||||
|
*.log
|
||||||
|
.env*
|
||||||
|
!.env.example
|
||||||
|
.DS_Store
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
FROM oven/bun:1.3.13 AS docs-build
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY docs/package.json docs/bun.lock ./
|
||||||
|
RUN --mount=type=cache,target=/root/.bun/install/cache bun install --frozen-lockfile --ignore-scripts --cache-dir=/root/.bun/install/cache
|
||||||
|
COPY docs ./
|
||||||
|
COPY CHANGELOG.md /CHANGELOG.md
|
||||||
|
RUN bun run postinstall
|
||||||
|
RUN bun run build
|
||||||
|
|
||||||
|
FROM node:22-bookworm-slim
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
ENV HOSTNAME=0.0.0.0
|
||||||
|
ENV PORT=3000
|
||||||
|
RUN groupadd --system --gid 1001 nodejs && useradd --system --uid 1001 --gid nodejs nextjs
|
||||||
|
COPY --from=docs-build --chown=nextjs:nodejs /app/public ./public
|
||||||
|
COPY --from=docs-build --chown=nextjs:nodejs /app/index.md ./index.md
|
||||||
|
COPY --from=docs-build --chown=nextjs:nodejs /app/content ./content
|
||||||
|
COPY --from=docs-build --chown=nextjs:nodejs /app/.source ./.source
|
||||||
|
COPY --from=docs-build --chown=nextjs:nodejs /app/source.config.ts ./source.config.ts
|
||||||
|
COPY --from=docs-build --chown=nextjs:nodejs /CHANGELOG.md /CHANGELOG.md
|
||||||
|
COPY --from=docs-build --chown=nextjs:nodejs /app/.next/standalone ./
|
||||||
|
COPY --from=docs-build --chown=nextjs:nodejs /app/.next/static ./.next/static
|
||||||
|
USER nextjs
|
||||||
|
EXPOSE 3000
|
||||||
|
CMD ["node", "server.js"]
|
||||||
+24
-19
@@ -3,25 +3,39 @@
|
|||||||
This is a Next.js application generated with
|
This is a Next.js application generated with
|
||||||
[Create Fumadocs](https://github.com/fuma-nama/fumadocs).
|
[Create Fumadocs](https://github.com/fuma-nama/fumadocs).
|
||||||
|
|
||||||
It is a Next.js app with [Static Export](https://nextjs.org/docs/app/guides/static-exports) configured.
|
It runs as a server-backed Next.js docs site and is configured for standalone
|
||||||
|
output. Route handlers such as search and LLM text remain available at runtime.
|
||||||
|
|
||||||
Run development server:
|
Run development server:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run dev
|
bun run dev
|
||||||
# or
|
|
||||||
pnpm dev
|
|
||||||
# or
|
|
||||||
yarn dev
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Open http://localhost:3000 with your browser to see the result.
|
Build and run local production server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bun run build
|
||||||
|
bun run start
|
||||||
|
```
|
||||||
|
|
||||||
|
Run the published image with Docker Compose:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
Or build locally with Docker Compose:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f docker-compose.local.yml up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
## Explore
|
## Explore
|
||||||
|
|
||||||
In the project, you can see:
|
In the project, you can see:
|
||||||
|
|
||||||
- `lib/source.ts`: Code for content source adapter, [`loader()`](https://fumadocs.dev/docs/headless/source-api) provides the interface to access your content.
|
- `lib/source.ts`: Code for content source adapter, `loader()` provides the interface to access your content.
|
||||||
- `lib/layout.shared.tsx`: Shared options for layouts, optional but preferred to keep.
|
- `lib/layout.shared.tsx`: Shared options for layouts, optional but preferred to keep.
|
||||||
|
|
||||||
| Route | Description |
|
| Route | Description |
|
||||||
@@ -32,16 +46,7 @@ In the project, you can see:
|
|||||||
|
|
||||||
### Fumadocs MDX
|
### Fumadocs MDX
|
||||||
|
|
||||||
A `source.config.ts` config file has been included, you can customise different options like frontmatter schema.
|
A `source.config.ts` config file has been included, you can customise different
|
||||||
|
options like frontmatter schema.
|
||||||
|
|
||||||
Read the [Introduction](https://fumadocs.dev/docs/mdx) for further details.
|
Read the [Introduction](https://fumadocs.dev/docs/mdx) for further details.
|
||||||
|
|
||||||
## Learn More
|
|
||||||
|
|
||||||
To learn more about Next.js and Fumadocs, take a look at the following
|
|
||||||
resources:
|
|
||||||
|
|
||||||
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js
|
|
||||||
features and API.
|
|
||||||
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
|
|
||||||
- [Fumadocs](https://fumadocs.dev) - learn about Fumadocs
|
|
||||||
|
|||||||
@@ -38,6 +38,24 @@ cp .env.example .env
|
|||||||
docker compose -f docker-compose.local.yml up -d --build
|
docker compose -f docker-compose.local.yml up -d --build
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 文档站镜像
|
||||||
|
|
||||||
|
文档站位于 `docs/` 目录,按带服务端能力的 Next.js standalone 应用单独构建,不打进主应用镜像。
|
||||||
|
|
||||||
|
使用发布镜像:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd docs
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
基于当前源码本地构建:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd docs
|
||||||
|
docker compose -f docker-compose.local.yml up -d --build
|
||||||
|
```
|
||||||
|
|
||||||
## 数据目录
|
## 数据目录
|
||||||
|
|
||||||
`docker-compose.yml` 会把本地 `./data` 挂载到容器内 `/app/data`,用于保存 SQLite 数据库、提示词数据和上传素材。
|
`docker-compose.yml` 会把本地 `./data` 挂载到容器内 `/app/data`,用于保存 SQLite 数据库、提示词数据和上传素材。
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ description: 当前版本已实现但仍需人工验证的变更项
|
|||||||
|
|
||||||
# 待测试
|
# 待测试
|
||||||
|
|
||||||
|
- 文档站搜索改为中英文混合 tokenizer,中文正文、标题和短语会按中文词、单字、二元和三元片段建立索引;需要验证 `/api/search` 和搜索弹窗能命中文档中的中文关键词。
|
||||||
|
- 文档站改为 Next.js standalone server 输出,新增 `docs/Dockerfile`、`docs/docker-compose.yml` 和 `docs/docker-compose.local.yml` 独立运行入口;需要验证文档页、搜索接口和 LLM 文本接口在文档站容器中可访问。
|
||||||
- 画布连线支持右键打开删除菜单;拖拽连线到目标卡片内部、连接点附近或卡片边缘外扩范围内会自动吸附并连接,拖到已有但不可连接的节点附近不会再误弹创建节点菜单。
|
- 画布连线支持右键打开删除菜单;拖拽连线到目标卡片内部、连接点附近或卡片边缘外扩范围内会自动吸附并连接,拖到已有但不可连接的节点附近不会再误弹创建节点菜单。
|
||||||
- 外部软件可通过 URL 查询参数 `baseUrl`/`baseurl` 和 `apiKey`/`apikey` 跳转到前端;读取后会从地址栏移除这些参数,后台允许自定义渠道时会自动切到自定义渠道、填入配置并打开配置弹窗,未允许时会打开配置弹窗并提示无法导入。
|
- 外部软件可通过 URL 查询参数 `baseUrl`/`baseurl` 和 `apiKey`/`apikey` 跳转到前端;读取后会从地址栏移除这些参数,后台允许自定义渠道时会自动切到自定义渠道、填入配置并打开配置弹窗,未允许时会打开配置弹窗并提示无法导入。
|
||||||
- 生图工作台和画布生图会把参考图按当前顺序显示为 `图片1`、`图片2` 等编号,并在图生图请求的实际提示词中注入编号说明;需要验证 `/image` 参考图排序、画布配置节点输入顺序和画布助手参考图编号一致。
|
- 生图工作台和画布生图会把参考图按当前顺序显示为 `图片1`、`图片2` 等编号,并在图生图请求的实际提示词中注入编号说明;需要验证 `/image` 参考图排序、画布配置节点输入顺序和画布助手参考图编号一致。
|
||||||
|
|||||||
@@ -15,4 +15,11 @@ description: 支持无限画布项目继续维护
|
|||||||
|
|
||||||
- 给 GitHub 仓库点 Star,帮助项目被更多人看到。
|
- 给 GitHub 仓库点 Star,帮助项目被更多人看到。
|
||||||
- 金额打赏,用于支持 AI 工具订阅、模型服务和项目维护成本。
|
- 金额打赏,用于支持 AI 工具订阅、模型服务和项目维护成本。
|
||||||
- Codex、Claude 等 AI 编程工具订阅账号支持,帮助维护者持续使用 AI 辅助开发。
|
- Codex、Claude 等 AI 编程工具订阅账号支持,帮助维护者持续使用 AI 辅助开发。
|
||||||
|
|
||||||
|
<div className="not-prose mt-6 flex flex-wrap items-center gap-3">
|
||||||
|
<span className="text-lg font-semibold text-fd-foreground">赞助地址:</span>
|
||||||
|
<a href="https://ifdian.net/a/basketikun" target="_blank" rel="noreferrer" className="inline-flex">
|
||||||
|
<img src="https://img.shields.io/badge/%E7%88%B1%E5%8F%91%E7%94%B5-%E8%B5%9E%E5%8A%A9%E4%BD%9C%E8%80%85-946ce6?style=for-the-badge&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0id2hpdGUiPjxwYXRoIGQ9Ik0xMiAyMS4zNWwtMS40NS0xLjMyQzUuNCAxNS4zNiAyIDEyLjI4IDIgOC41IDIgNS40MiA0LjQyIDMgNy41IDNjMS43NCAwIDMuNDEuODEgNC41IDIuMDlDMTMuMDkgMy44MSAxNC43NiAzIDE2LjUgMyAxOS41OCAzIDIyIDUuNDIgMjIgOC41YzAgMy43OC0zLjQgNi44Ni04LjU1IDExLjU0TDEyIDIxLjM1eiIvPjwvc3ZnPg==&logoColor=white" alt="爱发电赞助" />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
services:
|
||||||
|
docs:
|
||||||
|
build:
|
||||||
|
context: ..
|
||||||
|
dockerfile: docs/Dockerfile
|
||||||
|
ports:
|
||||||
|
- "3001:3000"
|
||||||
|
restart: unless-stopped
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
services:
|
||||||
|
docs:
|
||||||
|
image: ghcr.io/basketikun/infinite-canvas-docs:latest
|
||||||
|
ports:
|
||||||
|
- "3001:3000"
|
||||||
|
restart: unless-stopped
|
||||||
@@ -4,6 +4,7 @@ const withMDX = createMDX();
|
|||||||
|
|
||||||
/** @type {import('next').NextConfig} */
|
/** @type {import('next').NextConfig} */
|
||||||
const config = {
|
const config = {
|
||||||
|
output: 'standalone',
|
||||||
reactStrictMode: true,
|
reactStrictMode: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -5,7 +5,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
"start": "serve out",
|
"start": "next start",
|
||||||
"types:check": "fumadocs-mdx && next typegen && tsc --noEmit",
|
"types:check": "fumadocs-mdx && next typegen && tsc --noEmit",
|
||||||
"postinstall": "fumadocs-mdx"
|
"postinstall": "fumadocs-mdx"
|
||||||
},
|
},
|
||||||
@@ -31,4 +31,4 @@
|
|||||||
"tailwindcss": "^4.3.0",
|
"tailwindcss": "^4.3.0",
|
||||||
"typescript": "^6.0.3"
|
"typescript": "^6.0.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
import { source } from '@/lib/source';
|
import { source } from '@/lib/source';
|
||||||
|
import { createDocsSearchTokenizer } from '@/lib/search-tokenizer';
|
||||||
import { createFromSource } from 'fumadocs-core/search/server';
|
import { createFromSource } from 'fumadocs-core/search/server';
|
||||||
|
|
||||||
export const revalidate = false;
|
export const revalidate = false;
|
||||||
|
|
||||||
export const { staticGET: GET } = createFromSource(source, {
|
export const { staticGET: GET } = createFromSource(source, {
|
||||||
// https://docs.orama.com/docs/orama-js/supported-languages
|
components: {
|
||||||
language: 'english',
|
tokenizer: createDocsSearchTokenizer(),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,10 +2,19 @@
|
|||||||
@import 'fumadocs-ui/css/neutral.css';
|
@import 'fumadocs-ui/css/neutral.css';
|
||||||
@import 'fumadocs-ui/css/preset.css';
|
@import 'fumadocs-ui/css/preset.css';
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--font-body: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', Arial, sans-serif;
|
||||||
|
--font-display: 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', Arial, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
scrollbar-gutter: stable;
|
scrollbar-gutter: stable;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: var(--font-body);
|
||||||
|
}
|
||||||
|
|
||||||
html > body[data-scroll-locked] {
|
html > body[data-scroll-locked] {
|
||||||
margin-right: 0px !important;
|
margin-right: 0px !important;
|
||||||
--removed-body-scroll-bar-size: 0px !important;
|
--removed-body-scroll-bar-size: 0px !important;
|
||||||
|
|||||||
+1
-18
@@ -1,26 +1,9 @@
|
|||||||
import { Noto_Sans_SC, Sora } from 'next/font/google';
|
|
||||||
import { Provider } from '@/components/provider';
|
import { Provider } from '@/components/provider';
|
||||||
import './global.css';
|
import './global.css';
|
||||||
|
|
||||||
const bodyFont = Noto_Sans_SC({
|
|
||||||
subsets: ['latin'],
|
|
||||||
weight: ['400', '500', '600'],
|
|
||||||
variable: '--font-body',
|
|
||||||
});
|
|
||||||
|
|
||||||
const displayFont = Sora({
|
|
||||||
subsets: ['latin'],
|
|
||||||
weight: ['500', '600', '700'],
|
|
||||||
variable: '--font-display',
|
|
||||||
});
|
|
||||||
|
|
||||||
export default function Layout({ children }: LayoutProps<'/'>) {
|
export default function Layout({ children }: LayoutProps<'/'>) {
|
||||||
return (
|
return (
|
||||||
<html
|
<html lang="zh-CN" suppressHydrationWarning>
|
||||||
lang="zh-CN"
|
|
||||||
className={`${bodyFont.className} ${bodyFont.variable} ${displayFont.variable}`}
|
|
||||||
suppressHydrationWarning
|
|
||||||
>
|
|
||||||
<body className="flex flex-col min-h-screen">
|
<body className="flex flex-col min-h-screen">
|
||||||
<Provider>{children}</Provider>
|
<Provider>{children}</Provider>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
import { getPageImage, source } from '@/lib/source';
|
|
||||||
import { notFound } from 'next/navigation';
|
|
||||||
import { ImageResponse } from 'next/og';
|
|
||||||
import { generate as DefaultImage } from 'fumadocs-ui/og';
|
|
||||||
import { appName } from '@/lib/shared';
|
|
||||||
|
|
||||||
export const revalidate = false;
|
|
||||||
|
|
||||||
export async function GET(_req: Request, { params }: RouteContext<'/og/docs/[...slug]'>) {
|
|
||||||
const { slug } = await params;
|
|
||||||
const page = source.getPage(slug.slice(0, -1));
|
|
||||||
if (!page) notFound();
|
|
||||||
|
|
||||||
return new ImageResponse(
|
|
||||||
<DefaultImage title={page.data.title} description={page.data.description} site={appName} />,
|
|
||||||
{
|
|
||||||
width: 1200,
|
|
||||||
height: 630,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export function generateStaticParams() {
|
|
||||||
return source.getPages().map((page) => ({
|
|
||||||
lang: page.locale,
|
|
||||||
slug: getPageImage(page).segments,
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
@@ -13,12 +13,14 @@ import {
|
|||||||
import { useDocsSearch } from 'fumadocs-core/search/client';
|
import { useDocsSearch } from 'fumadocs-core/search/client';
|
||||||
import { create } from '@orama/orama';
|
import { create } from '@orama/orama';
|
||||||
import { useI18n } from 'fumadocs-ui/contexts/i18n';
|
import { useI18n } from 'fumadocs-ui/contexts/i18n';
|
||||||
|
import { createDocsSearchTokenizer } from '@/lib/search-tokenizer';
|
||||||
|
|
||||||
function initOrama() {
|
function initOrama() {
|
||||||
return create({
|
return create({
|
||||||
schema: { _: 'string' },
|
schema: { _: 'string' },
|
||||||
// https://docs.orama.com/docs/orama-js/supported-languages
|
components: {
|
||||||
language: 'english',
|
tokenizer: createDocsSearchTokenizer(),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { getMDXComponents } from '@/components/mdx';
|
import { getMDXComponents } from '@/components/mdx';
|
||||||
import { gitConfig } from '@/lib/shared';
|
import { gitConfig } from '@/lib/shared';
|
||||||
import { getPageImage, getPageMarkdownUrl, source } from '@/lib/source';
|
import { getPageMarkdownUrl, source } from '@/lib/source';
|
||||||
import type { Metadata } from 'next';
|
import type { Metadata } from 'next';
|
||||||
import { createRelativeLink } from 'fumadocs-ui/mdx';
|
import { createRelativeLink } from 'fumadocs-ui/mdx';
|
||||||
import { DocsBody, DocsDescription, DocsPage, DocsTitle } from 'fumadocs-ui/page';
|
import { DocsBody, DocsDescription, DocsPage, DocsTitle } from 'fumadocs-ui/page';
|
||||||
@@ -41,8 +41,5 @@ export function getDocPageMetadata(page: DocPageData): Metadata {
|
|||||||
return {
|
return {
|
||||||
title: page.data.title,
|
title: page.data.title,
|
||||||
description: page.data.description,
|
description: page.data.description,
|
||||||
openGraph: {
|
|
||||||
images: getPageImage(page).url,
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
type OramaTokenizer = {
|
||||||
|
language: string;
|
||||||
|
normalizationCache: Map<string, string>;
|
||||||
|
tokenize: (raw: string, language?: string, prop?: string, withCache?: boolean) => string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
const wordPattern = /[\p{Script=Han}]+|[a-z0-9][a-z0-9_'-]*/giu;
|
||||||
|
const hanPattern = /^\p{Script=Han}+$/u;
|
||||||
|
const chineseSegmenter = 'Segmenter' in Intl ? new Intl.Segmenter('zh-CN', { granularity: 'word' }) : null;
|
||||||
|
|
||||||
|
function getChineseSegments(value: string) {
|
||||||
|
if (!chineseSegmenter) return [];
|
||||||
|
|
||||||
|
return Array.from(chineseSegmenter.segment(value))
|
||||||
|
.filter((item) => item.isWordLike)
|
||||||
|
.map((item) => item.segment);
|
||||||
|
}
|
||||||
|
|
||||||
|
function addChineseTokens(tokens: string[], value: string) {
|
||||||
|
const chars = Array.from(value);
|
||||||
|
if (chars.length <= 12) tokens.push(value);
|
||||||
|
tokens.push(...getChineseSegments(value));
|
||||||
|
|
||||||
|
for (let size = 1; size <= 3; size += 1) {
|
||||||
|
if (chars.length < size) continue;
|
||||||
|
for (let i = 0; i <= chars.length - size; i += 1) {
|
||||||
|
tokens.push(chars.slice(i, i + size).join(''));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function createDocsSearchTokenizer(): OramaTokenizer {
|
||||||
|
return {
|
||||||
|
language: 'zh-CN',
|
||||||
|
normalizationCache: new Map(),
|
||||||
|
tokenize(raw) {
|
||||||
|
if (typeof raw !== 'string') return [raw];
|
||||||
|
|
||||||
|
const tokens: string[] = [];
|
||||||
|
const input = raw.normalize('NFKC').toLowerCase();
|
||||||
|
|
||||||
|
for (const match of input.matchAll(wordPattern)) {
|
||||||
|
const value = match[0];
|
||||||
|
if (hanPattern.test(value)) {
|
||||||
|
addChineseTokens(tokens, value);
|
||||||
|
} else {
|
||||||
|
tokens.push(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.from(new Set(tokens.filter(Boolean)));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
export const appName = '无限画布';
|
export const appName = '无限画布';
|
||||||
export const docsRoute = '/docs';
|
export const docsRoute = '/docs';
|
||||||
export const docsImageRoute = '/og/docs';
|
|
||||||
export const docsContentRoute = '/llms.mdx/docs';
|
export const docsContentRoute = '/llms.mdx/docs';
|
||||||
|
|
||||||
// fill this with your actual GitHub info, for example:
|
// fill this with your actual GitHub info, for example:
|
||||||
|
|||||||
+1
-10
@@ -1,6 +1,6 @@
|
|||||||
import { docs } from 'collections/server';
|
import { docs } from 'collections/server';
|
||||||
import { loader } from 'fumadocs-core/source';
|
import { loader } from 'fumadocs-core/source';
|
||||||
import { docsContentRoute, docsImageRoute, docsRoute } from './shared';
|
import { docsContentRoute, docsRoute } from './shared';
|
||||||
|
|
||||||
// See https://fumadocs.dev/docs/headless/source-api for more info
|
// See https://fumadocs.dev/docs/headless/source-api for more info
|
||||||
export const source = loader({
|
export const source = loader({
|
||||||
@@ -9,15 +9,6 @@ export const source = loader({
|
|||||||
plugins: [],
|
plugins: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
export function getPageImage(page: (typeof source)['$inferPage']) {
|
|
||||||
const segments = [...page.slugs, 'image.png'];
|
|
||||||
|
|
||||||
return {
|
|
||||||
segments,
|
|
||||||
url: `${docsImageRoute}/${segments.join('/')}`,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function getPageMarkdownUrl(page: (typeof source)['$inferPage']) {
|
export function getPageMarkdownUrl(page: (typeof source)['$inferPage']) {
|
||||||
const segments = [...page.slugs, 'content.md'];
|
const segments = [...page.slugs, 'content.md'];
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user