diff --git a/.dockerignore b/.dockerignore
index 81cbfb0..f637398 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -2,6 +2,10 @@
.idea
docs2
data
+docs/.next
+docs/.source
+docs/node_modules
+docs/out
web/.next
web/node_modules
web/out
diff --git a/.github/workflows/docs-docker-image.yml b/.github/workflows/docs-docker-image.yml
new file mode 100644
index 0000000..bf5e122
--- /dev/null
+++ b/.github/workflows/docs-docker-image.yml
@@ -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
diff --git a/CHANGELOG.md b/CHANGELOG.md
index cdf92d1..7654d07 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -3,15 +3,14 @@
## Unreleased
+ [新增] 新增文档站点页面。
++ [优化] 优化画布连线交互。
## v0.2.0 - 2026-06-01
-+ [新增] 支持通过火山方舟 Agent Plan 接入 Seedance 2.0 视频生成,后端会自动适配任务创建、轮询和结果回填接口。
-+ [新增] 视频创作台和画布视频生成支持 Seedance 分辨率、比例、时长、生成声音、水印,以及参考图片、参考视频、参考音频输入。
-+ [新增] 画布新增音频节点,并新增参考素材上传与公开访问接口,配合 `PUBLIC_BASE_URL` 供火山方舟拉取本地参考素材。
-+ [优化] 管理后台模型渠道支持 Agent Plan Base URL 归一、Seedance 模型配置提示、渠道探测超时和已启用模型自动同步到公开可用模型。
-+ [优化] 图片/视频参考素材会按 `图片1`、`视频1`、`音频1` 编号注入提示词,图像请求尺寸和质量参数也会在请求前归一校验。
-+ [修复] 修复 Docker 部署时 SQLite 相对路径可能写入错误目录的问题,并优化 AI 上游鉴权、限流和 Seedance 敏感参考视频错误提示。
++ [新增] 支持通过火山方舟AgentPlan接入。
++ [新增] 视频生成支持声音、水印及图片/视频/音频参考输入。
++ [新增] 画布新增音频节点。
++ [优化] 图片/视频素材支持 `图片1`编号注入提示词。
## v0.1.1 - 2026-05-30
diff --git a/Dockerfile b/Dockerfile
index 1bb5cbb..9e89b32 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -3,7 +3,7 @@ FROM oven/bun:1.3.13 AS web-build
WORKDIR /app/web
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 CHANGELOG.md /app/CHANGELOG.md
COPY web ./
diff --git a/README.md b/README.md
index 64a30b9..1a57fa8 100644
--- a/README.md
+++ b/README.md
@@ -101,6 +101,23 @@ https://infinite-canvas-cpco.onrender.com?apiKey={key}&baseUrl={address}
- [系统配置数据结构](docs2/system-settings.md)
- [接口响应约定](docs2/api-response.md)
+## 赞助支持
+
+
+
+如果这个项目对你有帮助,欢迎通过爱发电赞助支持,你的每一份鼓励都是持续更新的动力!
+
+
+
+
+
+
+
+
+
+
+
+
## 社区支持
学 AI,上 L 站:[LinuxDO](https://linux.do/)
diff --git a/docs/.dockerignore b/docs/.dockerignore
new file mode 100644
index 0000000..31fea94
--- /dev/null
+++ b/docs/.dockerignore
@@ -0,0 +1,9 @@
+node_modules
+.next
+out
+.source
+coverage
+*.log
+.env*
+!.env.example
+.DS_Store
diff --git a/docs/Dockerfile b/docs/Dockerfile
new file mode 100644
index 0000000..c93ee3f
--- /dev/null
+++ b/docs/Dockerfile
@@ -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"]
diff --git a/docs/README.md b/docs/README.md
index d0aff64..ed5cf1f 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -3,25 +3,39 @@
This is a Next.js application generated with
[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:
```bash
-npm run dev
-# or
-pnpm dev
-# or
-yarn dev
+bun run 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
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.
| Route | Description |
@@ -32,16 +46,7 @@ In the project, you can see:
### 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.
-
-## 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
diff --git a/docs/content/docs/overview/docker.mdx b/docs/content/docs/overview/docker.mdx
index c942993..2ed4076 100644
--- a/docs/content/docs/overview/docker.mdx
+++ b/docs/content/docs/overview/docker.mdx
@@ -38,6 +38,24 @@ cp .env.example .env
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 数据库、提示词数据和上传素材。
diff --git a/docs/content/docs/progress/pending-test.mdx b/docs/content/docs/progress/pending-test.mdx
index 39296d4..1f0dd33 100644
--- a/docs/content/docs/progress/pending-test.mdx
+++ b/docs/content/docs/progress/pending-test.mdx
@@ -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` 跳转到前端;读取后会从地址栏移除这些参数,后台允许自定义渠道时会自动切到自定义渠道、填入配置并打开配置弹窗,未允许时会打开配置弹窗并提示无法导入。
- 生图工作台和画布生图会把参考图按当前顺序显示为 `图片1`、`图片2` 等编号,并在图生图请求的实际提示词中注入编号说明;需要验证 `/image` 参考图排序、画布配置节点输入顺序和画布助手参考图编号一致。
diff --git a/docs/content/docs/support/donate.mdx b/docs/content/docs/support/donate.mdx
index 4b47896..ed74a4e 100644
--- a/docs/content/docs/support/donate.mdx
+++ b/docs/content/docs/support/donate.mdx
@@ -15,4 +15,11 @@ description: 支持无限画布项目继续维护
- 给 GitHub 仓库点 Star,帮助项目被更多人看到。
- 金额打赏,用于支持 AI 工具订阅、模型服务和项目维护成本。
-- Codex、Claude 等 AI 编程工具订阅账号支持,帮助维护者持续使用 AI 辅助开发。
\ No newline at end of file
+- Codex、Claude 等 AI 编程工具订阅账号支持,帮助维护者持续使用 AI 辅助开发。
+
+
+
赞助地址:
+
+
+
+
diff --git a/docs/docker-compose.local.yml b/docs/docker-compose.local.yml
new file mode 100644
index 0000000..3715d87
--- /dev/null
+++ b/docs/docker-compose.local.yml
@@ -0,0 +1,8 @@
+services:
+ docs:
+ build:
+ context: ..
+ dockerfile: docs/Dockerfile
+ ports:
+ - "3001:3000"
+ restart: unless-stopped
diff --git a/docs/docker-compose.yml b/docs/docker-compose.yml
new file mode 100644
index 0000000..7f67c5e
--- /dev/null
+++ b/docs/docker-compose.yml
@@ -0,0 +1,6 @@
+services:
+ docs:
+ image: ghcr.io/basketikun/infinite-canvas-docs:latest
+ ports:
+ - "3001:3000"
+ restart: unless-stopped
diff --git a/docs/next.config.mjs b/docs/next.config.mjs
index 457dcf2..18ac6b8 100644
--- a/docs/next.config.mjs
+++ b/docs/next.config.mjs
@@ -4,6 +4,7 @@ const withMDX = createMDX();
/** @type {import('next').NextConfig} */
const config = {
+ output: 'standalone',
reactStrictMode: true,
};
diff --git a/docs/package.json b/docs/package.json
index ca6d539..e9322fd 100644
--- a/docs/package.json
+++ b/docs/package.json
@@ -5,7 +5,7 @@
"scripts": {
"build": "next build",
"dev": "next dev",
- "start": "serve out",
+ "start": "next start",
"types:check": "fumadocs-mdx && next typegen && tsc --noEmit",
"postinstall": "fumadocs-mdx"
},
@@ -31,4 +31,4 @@
"tailwindcss": "^4.3.0",
"typescript": "^6.0.3"
}
-}
\ No newline at end of file
+}
diff --git a/docs/src/app/api/search/route.ts b/docs/src/app/api/search/route.ts
index aaaff7f..fee8952 100644
--- a/docs/src/app/api/search/route.ts
+++ b/docs/src/app/api/search/route.ts
@@ -1,9 +1,11 @@
import { source } from '@/lib/source';
+import { createDocsSearchTokenizer } from '@/lib/search-tokenizer';
import { createFromSource } from 'fumadocs-core/search/server';
export const revalidate = false;
export const { staticGET: GET } = createFromSource(source, {
- // https://docs.orama.com/docs/orama-js/supported-languages
- language: 'english',
+ components: {
+ tokenizer: createDocsSearchTokenizer(),
+ },
});
diff --git a/docs/src/app/global.css b/docs/src/app/global.css
index f86f3c9..c7e79dc 100644
--- a/docs/src/app/global.css
+++ b/docs/src/app/global.css
@@ -2,10 +2,19 @@
@import 'fumadocs-ui/css/neutral.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 {
scrollbar-gutter: stable;
}
+body {
+ font-family: var(--font-body);
+}
+
html > body[data-scroll-locked] {
margin-right: 0px !important;
--removed-body-scroll-bar-size: 0px !important;
diff --git a/docs/src/app/layout.tsx b/docs/src/app/layout.tsx
index aad1430..2bab4df 100644
--- a/docs/src/app/layout.tsx
+++ b/docs/src/app/layout.tsx
@@ -1,26 +1,9 @@
-import { Noto_Sans_SC, Sora } from 'next/font/google';
import { Provider } from '@/components/provider';
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<'/'>) {
return (
-
+
{children}
diff --git a/docs/src/app/og/docs/[...slug]/route.tsx b/docs/src/app/og/docs/[...slug]/route.tsx
deleted file mode 100644
index 877166d..0000000
--- a/docs/src/app/og/docs/[...slug]/route.tsx
+++ /dev/null
@@ -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(
- ,
- {
- width: 1200,
- height: 630,
- },
- );
-}
-
-export function generateStaticParams() {
- return source.getPages().map((page) => ({
- lang: page.locale,
- slug: getPageImage(page).segments,
- }));
-}
diff --git a/docs/src/components/search.tsx b/docs/src/components/search.tsx
index 1f70420..c09e4e4 100644
--- a/docs/src/components/search.tsx
+++ b/docs/src/components/search.tsx
@@ -13,12 +13,14 @@ import {
import { useDocsSearch } from 'fumadocs-core/search/client';
import { create } from '@orama/orama';
import { useI18n } from 'fumadocs-ui/contexts/i18n';
+import { createDocsSearchTokenizer } from '@/lib/search-tokenizer';
function initOrama() {
return create({
schema: { _: 'string' },
- // https://docs.orama.com/docs/orama-js/supported-languages
- language: 'english',
+ components: {
+ tokenizer: createDocsSearchTokenizer(),
+ },
});
}
diff --git a/docs/src/lib/doc-page.tsx b/docs/src/lib/doc-page.tsx
index 5a9b3fd..dec7e96 100644
--- a/docs/src/lib/doc-page.tsx
+++ b/docs/src/lib/doc-page.tsx
@@ -1,6 +1,6 @@
import { getMDXComponents } from '@/components/mdx';
import { gitConfig } from '@/lib/shared';
-import { getPageImage, getPageMarkdownUrl, source } from '@/lib/source';
+import { getPageMarkdownUrl, source } from '@/lib/source';
import type { Metadata } from 'next';
import { createRelativeLink } from 'fumadocs-ui/mdx';
import { DocsBody, DocsDescription, DocsPage, DocsTitle } from 'fumadocs-ui/page';
@@ -41,8 +41,5 @@ export function getDocPageMetadata(page: DocPageData): Metadata {
return {
title: page.data.title,
description: page.data.description,
- openGraph: {
- images: getPageImage(page).url,
- },
};
}
diff --git a/docs/src/lib/search-tokenizer.ts b/docs/src/lib/search-tokenizer.ts
new file mode 100644
index 0000000..3d14cbb
--- /dev/null
+++ b/docs/src/lib/search-tokenizer.ts
@@ -0,0 +1,54 @@
+type OramaTokenizer = {
+ language: string;
+ normalizationCache: Map;
+ 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)));
+ },
+ };
+}
diff --git a/docs/src/lib/shared.ts b/docs/src/lib/shared.ts
index 83841e0..8d104f8 100644
--- a/docs/src/lib/shared.ts
+++ b/docs/src/lib/shared.ts
@@ -1,6 +1,5 @@
export const appName = '无限画布';
export const docsRoute = '/docs';
-export const docsImageRoute = '/og/docs';
export const docsContentRoute = '/llms.mdx/docs';
// fill this with your actual GitHub info, for example:
diff --git a/docs/src/lib/source.ts b/docs/src/lib/source.ts
index f5d0eae..81c05b0 100644
--- a/docs/src/lib/source.ts
+++ b/docs/src/lib/source.ts
@@ -1,6 +1,6 @@
import { docs } from 'collections/server';
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
export const source = loader({
@@ -9,15 +9,6 @@ export const source = loader({
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']) {
const segments = [...page.slugs, 'content.md'];