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'];