diff --git a/.env.lite.example b/.env.lite.example new file mode 100644 index 00000000..cfdcb56b --- /dev/null +++ b/.env.lite.example @@ -0,0 +1,45 @@ +# WeKnora Lite 配置模板 +# 复制此文件为 .env.lite 并按需修改 +# cp .env.lite.example .env.lite + +GIN_MODE=release + +# === 数据库 === +DB_DRIVER=sqlite +DB_PATH=./data/weknora.db + +# === 检索引擎(FTS5 + sqlite-vec)=== +RETRIEVE_DRIVER=sqlite + +# === 文件存储 === +STORAGE_TYPE=local +LOCAL_STORAGE_BASE_DIR=./data/files + +# === 流管理(内存,无 Redis)=== +STREAM_MANAGER_TYPE=memory + +# === LLM 服务 === +# Ollama 本地服务(默认地址,按需修改) +OLLAMA_BASE_URL=http://127.0.0.1:11434 +# 如使用其他 OpenAI 兼容服务,取消注释: +# OPENAI_API_KEY=sk-xxx +# OPENAI_BASE_URL=https://api.openai.com/v1 + +# === 安全配置(生产环境请务必修改!)=== +TENANT_AES_KEY=CHANGE-ME-32-char-secret-key!!!! +JWT_SECRET=CHANGE-ME-jwt-secret + +# === 功能开关 === +NEO4J_ENABLE=false +WEKNORA_SANDBOX_MODE=disabled +ENABLE_GRAPH_RAG=false +DISABLE_REGISTRATION=false + +# === 性能 === +CONCURRENCY_POOL_SIZE=3 + +# Docreader 地址 +DOCREADER_ADDR=127.0.0.1:50051 + +# Docreader 传输方式 +DOCREADER_TRANSPORT=grpc \ No newline at end of file diff --git a/.github/workflows/release-lite.yml b/.github/workflows/release-lite.yml new file mode 100644 index 00000000..eb6b0bf9 --- /dev/null +++ b/.github/workflows/release-lite.yml @@ -0,0 +1,246 @@ +name: Release Lite Binaries + +on: + push: + tags: + - "v*" + workflow_dispatch: + inputs: + tag: + description: "Release tag (e.g. v0.2.0)" + required: true + +concurrency: + group: release-lite-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: write + +env: + GO_VERSION: "1.24" + NODE_VERSION: "22" + +jobs: + # ── 1. Build frontend once, share as artifact ── + build-frontend: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: ${{ env.NODE_VERSION }} + cache: npm + cache-dependency-path: frontend/package-lock.json + + - name: Build frontend + working-directory: frontend + run: | + npm ci + npm run build + + - uses: actions/upload-artifact@v4 + with: + name: frontend-dist + path: frontend/dist/ + retention-days: 1 + + # ── 2. Build Go binary per platform (native CGO) ── + build-binary: + needs: build-frontend + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + goos: linux + goarch: amd64 + - os: ubuntu-24.04-arm + goos: linux + goarch: arm64 + - os: macos-13 + goos: darwin + goarch: amd64 + - os: macos-14 + goos: darwin + goarch: arm64 + runs-on: ${{ matrix.os }} + env: + CGO_ENABLED: 1 + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + cache: true + + - uses: actions/download-artifact@v4 + with: + name: frontend-dist + path: web/ + + - name: Resolve version + id: ver + run: | + if [ -n "${{ inputs.tag }}" ]; then + VERSION="${{ inputs.tag }}" + else + VERSION="${GITHUB_REF#refs/tags/}" + fi + echo "version=${VERSION}" >> "$GITHUB_OUTPUT" + echo "Building ${VERSION} for ${{ matrix.goos }}/${{ matrix.goarch }}" + + - name: Build + run: | + export EDITION=lite + eval "$(./scripts/get_version.sh env)" + LDFLAGS="-w -s $(./scripts/get_version.sh ldflags)" + export CGO_CFLAGS="-Wno-deprecated-declarations" + if [ "${{ matrix.goos }}" = "darwin" ]; then + export CGO_LDFLAGS="-Wl,-no_warn_duplicate_libraries" + fi + go build -tags "sqlite_fts5" -ldflags="${LDFLAGS}" \ + -o WeKnora-lite ./cmd/server + + - name: Package tarball + run: | + ARCHIVE="WeKnora-lite_${{ steps.ver.outputs.version }}_${{ matrix.goos }}_${{ matrix.goarch }}" + mkdir -p "${ARCHIVE}/web" + cp WeKnora-lite "${ARCHIVE}/" + cp -r web/* "${ARCHIVE}/web/" + cp .env.lite.example "${ARCHIVE}/" + cp docs/LITE.md "${ARCHIVE}/README.md" + if [ -d config ]; then + cp -r config "${ARCHIVE}/config" + fi + if [ -d migrations/sqlite ]; then + mkdir -p "${ARCHIVE}/migrations/sqlite" + cp -r migrations/sqlite/* "${ARCHIVE}/migrations/sqlite/" + fi + if [ -f deploy/weknora-lite.service ]; then + cp deploy/weknora-lite.service "${ARCHIVE}/" + fi + tar czf "${ARCHIVE}.tar.gz" "${ARCHIVE}" + shasum -a 256 "${ARCHIVE}.tar.gz" > "${ARCHIVE}.tar.gz.sha256" + + - uses: actions/upload-artifact@v4 + with: + name: release-${{ matrix.goos }}-${{ matrix.goarch }} + path: | + WeKnora-lite_*.tar.gz + WeKnora-lite_*.tar.gz.sha256 + retention-days: 3 + + # ── 3. Create GitHub Release ── + release: + needs: build-binary + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/download-artifact@v4 + with: + pattern: release-* + merge-multiple: true + + - name: Resolve version + id: ver + run: | + if [ -n "${{ inputs.tag }}" ]; then + VERSION="${{ inputs.tag }}" + else + VERSION="${GITHUB_REF#refs/tags/}" + fi + echo "version=${VERSION}" >> "$GITHUB_OUTPUT" + + - name: Generate release notes + id: notes + run: | + cat > notes.md << 'NOTES' + ## WeKnora Lite ${{ steps.ver.outputs.version }} + + 单二进制部署,零外部依赖(无需 Docker / PostgreSQL / Redis)。 + + ### 快速开始 + + ```bash + # 1. 解压 + tar xzf WeKnora-lite_${{ steps.ver.outputs.version }}__.tar.gz + cd WeKnora-lite_${{ steps.ver.outputs.version }}__ + + # 2. 配置 + cp .env.lite.example .env.lite + # 编辑 .env.lite,至少确认 OLLAMA_BASE_URL 正确 + + # 3. 启动 Ollama(如尚未运行) + ollama serve & + ollama pull qwen2.5:7b + ollama pull nomic-embed-text + + # 4. 运行 + set -a && source .env.lite && set +a + ./WeKnora-lite + # 访问 http://localhost:8080 + ``` + + ### 平台支持 + + | 文件 | 平台 | + |------|------| + | `WeKnora-lite_*_linux_amd64.tar.gz` | Linux x86_64 | + | `WeKnora-lite_*_linux_arm64.tar.gz` | Linux ARM64 | + | `WeKnora-lite_*_darwin_amd64.tar.gz` | macOS Intel | + | `WeKnora-lite_*_darwin_arm64.tar.gz` | macOS Apple Silicon | + + 详细文档见 [LITE.md](docs/LITE.md)。 + NOTES + + - name: Create release + env: + GH_TOKEN: ${{ github.token }} + run: | + gh release create "${{ steps.ver.outputs.version }}" \ + --title "WeKnora Lite ${{ steps.ver.outputs.version }}" \ + --notes-file notes.md \ + WeKnora-lite_*.tar.gz \ + WeKnora-lite_*.tar.gz.sha256 + + # ── 4. Update Homebrew Formula ── + update-homebrew: + needs: release + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + ref: main + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Resolve version + id: ver + run: | + if [ -n "${{ inputs.tag }}" ]; then + VERSION="${{ inputs.tag }}" + else + VERSION="${GITHUB_REF#refs/tags/}" + fi + echo "version=${VERSION}" >> "$GITHUB_OUTPUT" + + - name: Wait for release assets to be available + run: sleep 15 + + - name: Update Formula + run: ./scripts/update-homebrew-formula.sh "${{ steps.ver.outputs.version }}" + + - name: Commit and push + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add Formula/weknora-lite.rb + if git diff --cached --quiet; then + echo "No changes to Formula" + else + git commit -m "formula: update weknora-lite to ${{ steps.ver.outputs.version }}" + git push origin main + fi diff --git a/Formula/weknora-lite.rb b/Formula/weknora-lite.rb new file mode 100644 index 00000000..c6ca55c0 --- /dev/null +++ b/Formula/weknora-lite.rb @@ -0,0 +1,118 @@ +class WeknoraLite < Formula + desc "Knowledge base management system — single-binary Lite edition" + homepage "https://github.com/Tencent/WeKnora" + version "0.0.0" + license "Apache-2.0" + + on_macos do + on_arm do + url "https://github.com/Tencent/WeKnora/releases/download/v#{version}/WeKnora-lite_v#{version}_darwin_arm64.tar.gz" + sha256 "PLACEHOLDER" + end + on_intel do + url "https://github.com/Tencent/WeKnora/releases/download/v#{version}/WeKnora-lite_v#{version}_darwin_amd64.tar.gz" + sha256 "PLACEHOLDER" + end + end + + on_linux do + on_arm do + url "https://github.com/Tencent/WeKnora/releases/download/v#{version}/WeKnora-lite_v#{version}_linux_arm64.tar.gz" + sha256 "PLACEHOLDER" + end + on_intel do + url "https://github.com/Tencent/WeKnora/releases/download/v#{version}/WeKnora-lite_v#{version}_linux_amd64.tar.gz" + sha256 "PLACEHOLDER" + end + end + + def install + libexec.install "WeKnora-lite" + pkgshare.install "web" if File.directory?("web") + pkgshare.install "config" if File.directory?("config") + pkgshare.install ".env.lite.example" + doc.install "README.md" + pkgshare.install "migrations" if File.directory?("migrations") + + (bin/"weknora-lite").write <<~SH + #!/bin/bash + CONFIG_DIR="${WEKNORA_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/weknora}" + DATA_DIR="${WEKNORA_DATA_DIR:-${XDG_DATA_HOME:-$HOME/.local/share}/weknora}" + + mkdir -p "$DATA_DIR/files" "$CONFIG_DIR/config" 2>/dev/null + + if [ ! -f "$CONFIG_DIR/config/config.yaml" ]; then + cp -r "#{pkgshare}/config/" "$CONFIG_DIR/config/" + fi + + if [ ! -d "$CONFIG_DIR/migrations" ] && [ -d "#{pkgshare}/migrations" ]; then + ln -sf "#{pkgshare}/migrations" "$CONFIG_DIR/migrations" + fi + + if [ ! -f "$CONFIG_DIR/.env.lite" ]; then + cp "#{pkgshare}/.env.lite.example" "$CONFIG_DIR/.env.lite" + sed -i '' "s|DB_PATH=.*|DB_PATH=$DATA_DIR/weknora.db|" "$CONFIG_DIR/.env.lite" + sed -i '' "s|LOCAL_STORAGE_BASE_DIR=.*|LOCAL_STORAGE_BASE_DIR=$DATA_DIR/files|" "$CONFIG_DIR/.env.lite" + rm -f "$CONFIG_DIR/.env.lite-e" + echo "" + echo "已创建配置文件: $CONFIG_DIR/.env.lite" + echo "请根据需要编辑(如修改 LLM 地址、安全密钥等)。" + echo "" + fi + + set -a + source "$CONFIG_DIR/.env.lite" + set +a + + export DB_PATH="${DB_PATH:-$DATA_DIR/weknora.db}" + export LOCAL_STORAGE_BASE_DIR="${LOCAL_STORAGE_BASE_DIR:-$DATA_DIR/files}" + export WEKNORA_WEB_DIR="${WEKNORA_WEB_DIR:-#{pkgshare}/web}" + + cd "$CONFIG_DIR" + exec "#{libexec}/WeKnora-lite" "$@" + SH + end + + def post_install + (var/"weknora").mkpath + (var/"log").mkpath + end + + service do + run [bin/"weknora-lite"] + keep_alive true + working_dir var/"weknora" + log_path var/"log/weknora-lite.log" + error_log_path var/"log/weknora-lite.log" + end + + def caveats + <<~EOS + 前台运行: + weknora-lite + + 后台服务(推荐): + brew services start weknora-lite # 启动并开机自启 + brew services stop weknora-lite # 停止 + brew services restart weknora-lite # 重启 + brew services info weknora-lite # 查看状态 + + 日志: + #{var}/log/weknora-lite.log + + 首次运行会自动创建配置文件: + ~/.config/weknora/.env.lite + + 数据存储在: + ~/.local/share/weknora/ + + 如需修改配置(LLM 服务地址、安全密钥等): + $EDITOR ~/.config/weknora/.env.lite + brew services restart weknora-lite + EOS + end + + test do + assert_predicate bin/"weknora-lite", :executable? + end +end diff --git a/Makefile b/Makefile index 88faf527..691abf67 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: help build run test clean docker-build-app docker-build-docreader docker-build-frontend docker-build-all docker-run migrate-up migrate-down docker-restart docker-stop start-all stop-all start-ollama stop-ollama build-images build-images-app build-images-docreader build-images-frontend clean-images check-env list-containers pull-images show-platform dev-start dev-stop dev-restart dev-logs dev-status dev-app dev-frontend docs install-swagger +.PHONY: help build run test clean docker-build-app docker-build-docreader docker-build-frontend docker-build-all docker-run migrate-up migrate-down docker-restart docker-stop start-all stop-all start-ollama stop-ollama build-images build-images-app build-images-docreader build-images-frontend clean-images check-env list-containers pull-images show-platform dev-start dev-stop dev-restart dev-logs dev-status dev-app dev-frontend docs install-swagger build-lite run-lite package-lite # Show help help: @@ -56,6 +56,11 @@ help: @echo " dev-status 查看开发环境状态" @echo " dev-app 启动后端应用(本地运行,需先运行 dev-start)" @echo " dev-frontend 启动前端(本地运行,需先运行 dev-start)" + @echo "" + @echo "Lite 模式(零外部依赖):" + @echo " build-lite 构建 Lite 版本(先构建前端到 web/,再构建 Go;SKIP_FRONTEND=1 跳过前端)" + @echo " run-lite 构建并启动 Lite 版本" + @echo " package-lite 构建并打包 Lite 发行包(tarball)" # Go related variables BINARY_NAME=WeKnora @@ -231,6 +236,35 @@ build-prod: LDFLAGS="-X 'github.com/Tencent/WeKnora/internal/handler.Version=$$VERSION' -X 'github.com/Tencent/WeKnora/internal/handler.Edition=standard' -X 'github.com/Tencent/WeKnora/internal/handler.CommitID=$$COMMIT_ID' -X 'github.com/Tencent/WeKnora/internal/handler.BuildTime=$$BUILD_TIME' -X 'github.com/Tencent/WeKnora/internal/handler.GoVersion=$$GO_VERSION' -X 'google.golang.org/protobuf/reflect/protoregistry.conflictPolicy=warn'"; \ go build -ldflags="-w -s $$LDFLAGS" -o $(BINARY_NAME) $(MAIN_PATH) +# Build Lite version (single binary, SQLite + in-memory queue) +# 会先构建前端到 web/,再构建 Go 二进制;SKIP_FRONTEND=1 可跳过前端 +build-lite: + @if [ -f frontend/package.json ] && [ "$${SKIP_FRONTEND:-}" != "1" ]; then \ + echo ">> Building frontend for Lite..."; \ + (cd frontend && npm ci --prefer-offline && npm run build) && \ + rm -rf web && cp -r frontend/dist web; \ + elif [ "$${SKIP_FRONTEND:-}" = "1" ]; then \ + echo ">> Skipping frontend (SKIP_FRONTEND=1)"; \ + else \ + echo ">> No frontend/package.json, skipping frontend"; \ + fi + export EDITION=lite; \ + eval "$$(./scripts/get_version.sh env)"; \ + LDFLAGS="$$(./scripts/get_version.sh ldflags)"; \ + CGO_ENABLED=1 \ + CGO_CFLAGS="-Wno-deprecated-declarations" \ + CGO_LDFLAGS="-Wl,-no_warn_duplicate_libraries" \ + go build -tags "sqlite_fts5" -ldflags="-w -s $$LDFLAGS" -o $(BINARY_NAME)-lite $(MAIN_PATH) + +# Run Lite version with .env.lite defaults +run-lite: build-lite + @if [ ! -f .env.lite ]; then echo "Error: .env.lite not found"; exit 1; fi + @set -a && . ./.env.lite && set +a && ./$(BINARY_NAME)-lite + +# Package Lite version into distributable tarball +package-lite: + ./scripts/package-lite.sh + download_spatial: go run cmd/download/duckdb/duckdb.go diff --git a/deploy/weknora-lite.service b/deploy/weknora-lite.service new file mode 100644 index 00000000..dac3bb29 --- /dev/null +++ b/deploy/weknora-lite.service @@ -0,0 +1,23 @@ +[Unit] +Description=WeKnora Lite - Knowledge Base Management System +After=network.target + +[Service] +Type=simple +User=weknora +Group=weknora +WorkingDirectory=/opt/weknora +EnvironmentFile=/opt/weknora/.env.lite +ExecStart=/opt/weknora/WeKnora-lite +Restart=on-failure +RestartSec=5 +LimitNOFILE=65536 + +# Security hardening +NoNewPrivileges=true +ProtectSystem=strict +ProtectHome=true +ReadWritePaths=/opt/weknora/data + +[Install] +WantedBy=multi-user.target diff --git a/docs/LITE.md b/docs/LITE.md new file mode 100644 index 00000000..deb48caf --- /dev/null +++ b/docs/LITE.md @@ -0,0 +1,165 @@ +# WeKnora Lite + +零外部依赖的单二进制部署模式。无需 Docker、PostgreSQL、Redis,适合快速体验和小规模私有部署。 + +## 架构 + +| 组件 | 标准版 | Lite 版 | +|------|--------|---------| +| 数据库 | PostgreSQL | SQLite (WAL) | +| 向量检索 | pgvector / Qdrant / ES | sqlite-vec (vec0) | +| 关键词检索 | ParadeDB BM25 / ES | SQLite FTS5 | +| 消息队列 | Redis + Asynq | 内存 SyncTaskExecutor | +| 会话存储 | Redis | 内存 | +| 流管理 | Redis / 内存 | 内存 | +| 文件存储 | MinIO / COS / 本地 | 本地 | +| 文档解析 | DocReader (gRPC) | 不可用(文本/段落导入可用)| +| 前端 | Nginx 容器 | Go 内置静态文件服务 | + +## 快速开始 + +### 方式一:Homebrew 安装(macOS / Linux,推荐) + +```bash +brew tap Tencent/weknora https://github.com/Tencent/WeKnora +brew install weknora-lite +``` + +安装完成后,推荐使用 **brew services** 以后台服务方式运行: + +```bash +brew services start weknora-lite # 启动服务(开机自动启动) +brew services info weknora-lite # 查看运行状态 +# 首次启动自动创建配置文件 ~/.config/weknora/.env.lite +# 数据存储在 ~/.local/share/weknora/ +# 访问 http://localhost:8080 +``` + +常用服务管理命令: + +```bash +brew services stop weknora-lite # 停止服务 +brew services restart weknora-lite # 重启服务(修改配置后需重启) +brew services info weknora-lite # 查看状态 +``` + +日志位于 `$(brew --prefix)/var/log/weknora-lite.log`。 + +也可以前台直接运行: + +```bash +weknora-lite +``` + +如需修改配置(LLM 服务地址、安全密钥等): + +```bash +$EDITOR ~/.config/weknora/.env.lite +brew services restart weknora-lite # 修改配置后重启生效 +``` + +> **LLM 服务**:WeKnora Lite 需要一个 OpenAI 兼容的 LLM 服务来提供对话和 Embedding 能力。 +> 可以使用 [Ollama](https://ollama.com/)(本地)、通义千问、OpenAI 等任何兼容服务, +> 在配置文件中设置对应的地址和 API Key 即可。 + +### 方式二:下载预编译包 + +从 [GitHub Releases](https://github.com/Tencent/WeKnora/releases) 下载对应平台的 tarball: + +| 文件 | 平台 | +|------|------| +| `WeKnora-lite_*_linux_amd64.tar.gz` | Linux x86_64 | +| `WeKnora-lite_*_linux_arm64.tar.gz` | Linux ARM64 | +| `WeKnora-lite_*_darwin_amd64.tar.gz` | macOS Intel | +| `WeKnora-lite_*_darwin_arm64.tar.gz` | macOS Apple Silicon | + +```bash +# 1. 解压 +tar xzf WeKnora-lite_v0.2.0_darwin_arm64.tar.gz +cd WeKnora-lite_v0.2.0_darwin_arm64 + +# 2. 配置 +cp .env.lite.example .env.lite +# 编辑 .env.lite,配置 LLM 服务地址和安全密钥 + +# 3. 运行 +set -a && source .env.lite && set +a +./WeKnora-lite +# 访问 http://localhost:8080 +``` + +### 方式三:从源码构建 + +前置条件:Go 1.22+(需要 CGO)、C 编译器 (gcc/clang)、Node.js 22+(前端构建)。 + +```bash +make run-lite +``` + +## 配置 + +Lite 模式通过 `.env.lite` 文件配置(模板见 `.env.lite.example`)。关键环境变量: + +```bash +DB_DRIVER=sqlite # 使用 SQLite +DB_PATH=./data/weknora.db # 数据库文件路径 +RETRIEVE_DRIVER=sqlite # SQLite 检索引擎 (FTS5 + sqlite-vec) +STORAGE_TYPE=local # 本地文件存储 +LOCAL_STORAGE_BASE_DIR=./data/files +STREAM_MANAGER_TYPE=memory # 内存流管理 +# REDIS_ADDR= # 留空 = 不使用 Redis +OLLAMA_BASE_URL=http://127.0.0.1:11434 +``` + +完整配置参见 [.env.lite.example](../.env.lite.example)。 + +## 后台运行 + +### Homebrew 用户(macOS / Linux) + +Homebrew 安装后直接使用 `brew services` 管理,详见上方「快速开始 → 方式一」。 + +### Linux systemd(tarball 安装) + +tarball 中附带 `weknora-lite.service` 模板,按需修改路径后安装: + +```bash +# 创建用户和目录 +sudo useradd -r -s /sbin/nologin weknora +sudo mkdir -p /opt/weknora/data +sudo cp WeKnora-lite web/ .env.lite /opt/weknora/ +sudo chown -R weknora:weknora /opt/weknora + +# 安装并启动服务 +sudo cp weknora-lite.service /etc/systemd/system/ +sudo systemctl daemon-reload +sudo systemctl enable --now weknora-lite + +# 管理 +sudo systemctl status weknora-lite # 查看状态 +sudo journalctl -u weknora-lite -f # 查看日志 +``` + +## 功能限制 + +与标准版相比,Lite 版有以下限制: + +- **文档解析**:不支持文件上传和 URL 导入的自动解析(PDF/Word/Excel 等)。可使用文本和段落方式手动导入。 +- **向量检索**:sqlite-vec 使用精确 KNN(非近似),适合 10 万条以下的小规模数据集。 +- **并发**:SQLite 单写者模型,高并发写入场景下性能不如 PostgreSQL。 +- **任务队列**:无持久化队列,进程重启后未完成的异步任务会丢失。 +- **知识图谱**:默认禁用 (`NEO4J_ENABLE=false`)。 +- **Agent Skills 沙箱**:默认禁用 (`WEKNORA_SANDBOX_MODE=disabled`)。 + +## 数据目录 + +默认所有数据存储在 `./data/` 目录下: + +``` +data/ +├── weknora.db # SQLite 数据库 +├── weknora.db-wal # WAL 日志 +└── files/ # 上传文件 +``` + +备份只需复制整个 `data/` 目录。 diff --git a/frontend/src/components/menu.vue b/frontend/src/components/menu.vue index 5c3220a1..bbdc6830 100644 --- a/frontend/src/components/menu.vue +++ b/frontend/src/components/menu.vue @@ -4,6 +4,7 @@
+ Lite