refactor(shell): Phase 3c——OCR 采集与浮层生命周期外置(OCR 簇收官) #232
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Desktop Build | |
| # Builds desktop installers with the backend bundled in (Epic #18 T2): | |
| # Spring Boot jar (per-platform javacpp natives) + jlink-trimmed JRE, so the | |
| # installer works without a local Java/PostgreSQL environment. | |
| on: | |
| workflow_dispatch: | |
| push: | |
| tags: ['v*'] | |
| pull_request: | |
| paths: | |
| - '.github/workflows/desktop-build.yml' | |
| - 'desktop/**' | |
| - 'backend/**' | |
| # Installers bundle the built frontend (frontend/dist), so frontend-only | |
| # changes affect the installer too and must run the build + smoke test. | |
| # Without this, the first-run wizard (PR #25, frontend-only) shipped to | |
| # v0.2.0 having never been validated inside an installer. See issue #27. | |
| - 'frontend/**' | |
| # The "Attach to GitHub Release" step (softprops/action-gh-release) creates the | |
| # release for a v* tag, which needs write access to repository contents. The | |
| # default GITHUB_TOKEN is read-only, so without this the tag build fails with | |
| # "403 Resource not accessible by integration". | |
| permissions: | |
| contents: write | |
| jobs: | |
| build: | |
| name: Build (${{ matrix.os }}) | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| # macOS runners take 1h+ per build (jlink + dmg + notarization), so PRs | |
| # only build Windows; macOS builds run on v* release tags and manual | |
| # workflow_dispatch runs. | |
| os: ${{ fromJSON((startsWith(github.ref, 'refs/tags/v') || github.event_name == 'workflow_dispatch') && '["macos-latest", "windows-latest"]' || '["windows-latest"]') }} | |
| runs-on: ${{ matrix.os }} | |
| steps: | |
| - uses: actions/checkout@v4 | |
| - uses: actions/setup-node@v4 | |
| with: | |
| node-version: '20' | |
| - uses: actions/setup-java@v4 | |
| id: jdk | |
| with: | |
| distribution: temurin | |
| java-version: '21' | |
| cache: maven | |
| - name: Build frontend | |
| working-directory: frontend | |
| run: | | |
| npm ci || npm install | |
| npm run build:h5 | |
| # Epic #43: build the embedded LibreOffice editor bundle (dist/zetaoffice), | |
| # shipped via extraResources for the experimental "LibreOffice 验证" window. | |
| npm run build:zetaoffice | |
| - name: Bake LOWA runtime + OFL CJK font into the editor bundle (offline) | |
| shell: bash | |
| env: | |
| # Self-built zh-CN LOWA engine (issue #66): native Chinese UI incl. Qt | |
| # tooltips + FS/callMain exports. Hosted on the project site (versioned, | |
| # immutable; wasm/data served brotli so the installer stays small). | |
| # Unset -> upstream CDN (English UI). Build recipe: desktop/lowa-build/. | |
| LOWA_BASE_URL: https://www.aiworkdeck.com/lowa-engine/24.2.8-zhcn-r2/ | |
| run: | | |
| # Track A (Epic #43): download the LibreOffice WASM runtime (soffice.*) | |
| # into dist/zetaoffice/lowa and an OFL-licensed CJK font (Noto Sans SC) | |
| # as cjk.ttc, so the packaged app renders Chinese OFFLINE without | |
| # reaching cdn.zetaoffice.net. Replaces the old best-effort copy of a | |
| # system font (non-deterministic, absent offline). Adds ~60 MB to the | |
| # installer. The local HTTP server (zetaoffice-server.js) serves these | |
| # first and falls back to the CDN only for anything not bundled. | |
| node desktop/scripts/fetch-lowa-assets.js | |
| ls -la frontend/dist/zetaoffice/lowa frontend/dist/zetaoffice/cjk.ttc | |
| - name: Install desktop dependencies | |
| working-directory: desktop | |
| run: npm ci | |
| - name: Desktop unit tests | |
| working-directory: desktop | |
| run: npm test | |
| - name: Bundle backend (macOS arm64) | |
| if: runner.os == 'macOS' | |
| env: | |
| JAVA_HOME: ${{ steps.jdk.outputs.path }} | |
| run: | | |
| # Apple Silicon only(2026-07-03 决策放弃 Intel Mac):单 arm64 jar + arm64 JRE | |
| mvn -B -q -DskipTests -Djavacpp.platform=macosx-arm64 -f backend/pom.xml package | |
| node desktop/scripts/prepare-backend.js \ | |
| --jar backend/target/backend-0.0.1-SNAPSHOT.jar \ | |
| --out desktop/bundled/mac-arm64 | |
| - name: Bundle pptx-service (macOS arm64) | |
| if: runner.os == 'macOS' | |
| run: | | |
| node desktop/scripts/prepare-python-service.js \ | |
| --service pptx-service \ | |
| --src pptx-service/backend \ | |
| --requirements pptx-service/requirements.lock \ | |
| --out desktop/bundled/mac-arm64 | |
| - name: Bundle mineru-service (macOS arm64) | |
| if: runner.os == 'macOS' | |
| run: | | |
| # 纯 pip 包服务(无 --src);模型不进包,首启在「组件管理」下载 | |
| node desktop/scripts/prepare-python-service.js \ | |
| --service mineru-service \ | |
| --requirements mineru-service/requirements.lock \ | |
| --out desktop/bundled/mac-arm64 | |
| du -sh desktop/bundled/mac-arm64/pysvc/mineru-service/lib | |
| - name: Bundle kokoro-service (macOS arm64) | |
| if: runner.os == 'macOS' | |
| run: | | |
| # 本地 TTS 包装层(Phase 3):模型不进包,首启在「组件管理」下载 | |
| node desktop/scripts/prepare-python-service.js \ | |
| --service kokoro-service \ | |
| --src kokoro-service \ | |
| --requirements kokoro-service/requirements.lock \ | |
| --out desktop/bundled/mac-arm64 | |
| du -sh desktop/bundled/mac-arm64/pysvc/kokoro-service/lib | |
| - name: Bundle backend (Windows x64) | |
| if: runner.os == 'Windows' | |
| shell: bash | |
| env: | |
| JAVA_HOME: ${{ steps.jdk.outputs.path }} | |
| run: | | |
| mvn -B -q -DskipTests -Djavacpp.platform=windows-x86_64 -f backend/pom.xml package | |
| node desktop/scripts/prepare-backend.js \ | |
| --jar backend/target/backend-0.0.1-SNAPSHOT.jar \ | |
| --out desktop/bundled/win-x64 | |
| - name: Bundle pptx-service (Windows x64) | |
| if: runner.os == 'Windows' | |
| shell: bash | |
| run: | | |
| node desktop/scripts/prepare-python-service.js \ | |
| --service pptx-service \ | |
| --src pptx-service/backend \ | |
| --requirements pptx-service/requirements.lock \ | |
| --out desktop/bundled/win-x64 | |
| - name: Bundle mineru-service (Windows x64) | |
| if: runner.os == 'Windows' | |
| shell: bash | |
| run: | | |
| node desktop/scripts/prepare-python-service.js \ | |
| --service mineru-service \ | |
| --requirements mineru-service/requirements.lock \ | |
| --out desktop/bundled/win-x64 | |
| du -sh desktop/bundled/win-x64/pysvc/mineru-service/lib | |
| - name: Bundle kokoro-service (Windows x64) | |
| if: runner.os == 'Windows' | |
| shell: bash | |
| run: | | |
| node desktop/scripts/prepare-python-service.js \ | |
| --service kokoro-service \ | |
| --src kokoro-service \ | |
| --requirements kokoro-service/requirements.lock \ | |
| --out desktop/bundled/win-x64 | |
| du -sh desktop/bundled/win-x64/pysvc/kokoro-service/lib | |
| - name: Sign bundled backend natives (macOS) | |
| if: runner.os == 'macOS' | |
| env: | |
| CSC_LINK: ${{ secrets.CSC_LINK }} | |
| CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }} | |
| run: | | |
| # Notarization rejects the adhoc-signed bytedeco dylibs inside the | |
| # fat jar (and any unsigned JRE binaries), so they must carry our | |
| # Developer ID signature before electron-builder seals the app. | |
| if [ -z "$CSC_LINK" ]; then | |
| echo "No signing cert (fork PR) — skipping native signing" | |
| exit 0 | |
| fi | |
| KC="$RUNNER_TEMP/awd-sign.keychain-db" | |
| KCPASS="awd-tmp-keychain" | |
| echo "$CSC_LINK" | base64 --decode > "$RUNNER_TEMP/awd-cert.p12" | |
| security create-keychain -p "$KCPASS" "$KC" | |
| security unlock-keychain -p "$KCPASS" "$KC" | |
| security import "$RUNNER_TEMP/awd-cert.p12" -k "$KC" -P "$CSC_KEY_PASSWORD" -T /usr/bin/codesign | |
| security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KCPASS" "$KC" > /dev/null | |
| security list-keychains -d user -s "$KC" login.keychain-db | |
| IDENTITY=$(security find-identity -v -p codesigning "$KC" | awk 'NR==1{print $2}') | |
| echo "Signing with identity: $IDENTITY" | |
| bash desktop/scripts/sign-mac-natives.sh "$IDENTITY" desktop/bundled/mac-arm64 desktop/build/entitlements.mac.plist | |
| - name: Smoke test bundled backend (macOS) | |
| if: runner.os == 'macOS' | |
| env: | |
| # The backend's WebTools pre-warm downloads chromium/firefox/webkit | |
| # from playwright.azureedge.net on every boot; the 2026-07-03 CDN | |
| # outage (400 GatewayExceptionResponse) pushed startup past 120s and | |
| # failed this step on both platforms. The smoke test only curls the | |
| # wizard endpoint — no browser needed — so skip the download to | |
| # remove the live network dependency (playwright-java's Driver | |
| # honors this env var). | |
| PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1' | |
| run: | | |
| # Boot the signed jar on the trimmed JRE exactly as the packaged app | |
| # will — catches runtime/JRE/signing breakage that packaging alone | |
| # cannot. (Cannot be tested on SIP-disabled dev machines: JDK-8326663.) | |
| mkdir -p "$RUNNER_TEMP/awd-home" | |
| desktop/bundled/mac-arm64/jre/bin/java \ | |
| -Duser.home="$RUNNER_TEMP/awd-home" \ | |
| -jar desktop/bundled/mac-arm64/backend.jar \ | |
| --spring.profiles.active=desktop --server.port=9696 \ | |
| > "$RUNNER_TEMP/backend-smoke.log" 2>&1 & | |
| BACK_PID=$! | |
| for i in $(seq 1 60); do | |
| sleep 2 | |
| if curl -sf -o /dev/null http://127.0.0.1:9696/api/admin/wizard; then | |
| echo "backend up after ~$((i*2))s" | |
| curl -s http://127.0.0.1:9696/api/admin/wizard; echo | |
| kill $BACK_PID | |
| exit 0 | |
| fi | |
| done | |
| echo "backend failed to start within 120s" | |
| tail -80 "$RUNNER_TEMP/backend-smoke.log" | |
| kill $BACK_PID 2>/dev/null || true | |
| exit 1 | |
| - name: Smoke test bundled pptx-service (macOS) | |
| if: runner.os == 'macOS' | |
| run: | | |
| # 用烙好的运行时按打包态同款方式拉起:先 alembic 迁移,后起服务,验 /health | |
| export PPTX_DATA_DIR="$RUNNER_TEMP/pptx-data" | |
| mkdir -p "$PPTX_DATA_DIR" | |
| APP="$PWD/desktop/bundled/mac-arm64/pysvc/pptx-service/app" | |
| PY="$PWD/desktop/bundled/mac-arm64/python/bin/python3.11" | |
| export PYTHONPATH="$PWD/desktop/bundled/mac-arm64/pysvc/pptx-service/lib" | |
| (cd "$APP" && "$PY" -m alembic -c alembic.ini upgrade head) | |
| PORT=5099 BACKEND_PORT=5099 FLASK_ENV=production "$PY" "$APP/app.py" > "$RUNNER_TEMP/pptx-smoke.log" 2>&1 & | |
| PPTX_PID=$! | |
| for i in $(seq 1 30); do | |
| sleep 2 | |
| if curl -sf http://127.0.0.1:5099/health; then | |
| echo; echo "pptx-service up after ~$((i*2))s" | |
| kill $PPTX_PID | |
| exit 0 | |
| fi | |
| done | |
| echo "pptx-service failed to start within 60s" | |
| tail -80 "$RUNNER_TEMP/pptx-smoke.log" | |
| kill $PPTX_PID 2>/dev/null || true | |
| exit 1 | |
| - name: Smoke test bundled mineru-service (macOS) | |
| if: runner.os == 'macOS' | |
| run: | | |
| # 无模型拉起(模型解析时懒加载,/docs 不依赖模型——本地实测约 4s 就绪) | |
| export MDIR="$RUNNER_TEMP/mineru-data" | |
| mkdir -p "$MDIR" | |
| PY="$PWD/desktop/bundled/mac-arm64/python/bin/python3.11" | |
| export PYTHONPATH="$PWD/desktop/bundled/mac-arm64/pysvc/mineru-service/lib" | |
| MINERU_DEVICE_MODE=cpu MINERU_MODEL_SOURCE=modelscope \ | |
| MODELSCOPE_CACHE="$MDIR" HF_HOME="$MDIR/hf" MINERU_TOOLS_CONFIG_JSON="$MDIR/mineru.json" \ | |
| "$PY" -m mineru.cli.fast_api --host 127.0.0.1 --port 8098 > "$RUNNER_TEMP/mineru-smoke.log" 2>&1 & | |
| MINERU_PID=$! | |
| for i in $(seq 1 30); do | |
| sleep 2 | |
| if curl -sf -o /dev/null http://127.0.0.1:8098/docs; then | |
| echo "mineru-service up after ~$((i*2))s" | |
| kill $MINERU_PID | |
| exit 0 | |
| fi | |
| done | |
| echo "mineru-service failed to start within 60s" | |
| tail -80 "$RUNNER_TEMP/mineru-smoke.log" | |
| kill $MINERU_PID 2>/dev/null || true | |
| exit 1 | |
| - name: Smoke test bundled kokoro-service (macOS) | |
| if: runner.os == 'macOS' | |
| run: | | |
| # 无模型拉起(模型懒加载,/health 与 voices 不依赖模型) | |
| PY="$PWD/desktop/bundled/mac-arm64/python/bin/python3.11" | |
| APP="$PWD/desktop/bundled/mac-arm64/pysvc/kokoro-service/app" | |
| export PYTHONPATH="$PWD/desktop/bundled/mac-arm64/pysvc/kokoro-service/lib" | |
| PORT=8881 "$PY" "$APP/app.py" > "$RUNNER_TEMP/kokoro-smoke.log" 2>&1 & | |
| KOKORO_PID=$! | |
| for i in $(seq 1 20); do | |
| sleep 2 | |
| if curl -sf http://127.0.0.1:8881/health && curl -sf http://127.0.0.1:8881/v1/audio/voices | grep -q zf_001; then | |
| echo; echo "kokoro-service up after ~$((i*2))s" | |
| kill $KOKORO_PID | |
| exit 0 | |
| fi | |
| done | |
| echo "kokoro-service failed to start within 40s" | |
| tail -60 "$RUNNER_TEMP/kokoro-smoke.log" | |
| kill $KOKORO_PID 2>/dev/null || true | |
| exit 1 | |
| - name: Pack pysvc archive (macOS) | |
| if: runner.os == 'macOS' | |
| run: | | |
| # 上万个 Python 小文件不再直接进 .app:签名(sign-mac-natives.sh 已给 | |
| # 每个 Mach-O 打 Developer ID + 时间戳——公证会扫嵌套压缩包,未签名照拒) | |
| # 与冒烟测试之后整体打成单个 pysvc.tar.gz,electron-builder 的签名/公证 | |
| # 阶段从上万文件降到一个文件,消除时间戳服务抖动。首启由主进程解压。 | |
| node desktop/scripts/pack-pysvc.js --bundle desktop/bundled/mac-arm64 | |
| - name: Package installers (macOS, signed & notarized) | |
| if: runner.os == 'macOS' | |
| working-directory: desktop | |
| env: | |
| CSC_LINK: ${{ secrets.CSC_LINK }} | |
| CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }} | |
| APPLE_ID: ${{ secrets.APPLE_ID }} | |
| APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} | |
| APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} | |
| # GitHub never exposes secrets to fork PRs, so CSC_LINK is empty | |
| # there — and force-signing with an empty CSC_LINK makes | |
| # electron-builder fail ("not a file"). Only force signing when the | |
| # cert secret is actually present; fork PRs fall back to | |
| # electron-builder's default unsigned PR build. | |
| CSC_FOR_PULL_REQUEST: ${{ secrets.CSC_LINK != '' }} | |
| run: | | |
| # mineru lib(transformers 等)有数万个小文件,macOS 默认 fd 上限 256 会让 | |
| # electron-builder 在打包/装订阶段 EMFILE。65536 实测仍复现——内核级 | |
| # kern.maxfilesperproc(默认 61440)同样要抬,三层一起调并打印生效值 | |
| sudo sysctl -w kern.maxfiles=524288 kern.maxfilesperproc=524288 || true | |
| sudo launchctl limit maxfiles 524288 524288 || true | |
| ulimit -n 524288 || ulimit -n "$(ulimit -Hn)" || true | |
| echo "fd soft=$(ulimit -n) hard=$(ulimit -Hn)" | |
| npx electron-builder --publish never | |
| - name: Notarization failure log (macOS) | |
| if: failure() && runner.os == 'macOS' | |
| env: | |
| APPLE_ID: ${{ secrets.APPLE_ID }} | |
| APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }} | |
| # NOT the APPLE_TEAM_ID secret: notarytool history rejects it with | |
| # 403 (it works for submission but not for the history API); the | |
| # team id is public anyway (package.json mac.notarize.teamId) | |
| TEAM_ID: X9B97KVA84 | |
| run: | | |
| # Surface Apple's per-file rejection reasons; without this the build | |
| # log only says "Invalid" | |
| HISTORY=$(xcrun notarytool history --apple-id "$APPLE_ID" \ | |
| --password "$APPLE_APP_SPECIFIC_PASSWORD" --team-id "$TEAM_ID" \ | |
| --output-format json || true) | |
| echo "$HISTORY" | head -c 2000; echo | |
| for SUB_ID in $(echo "$HISTORY" | jq -r '.history[0:2][].id // empty'); do | |
| echo "=== notarization log for $SUB_ID" | |
| xcrun notarytool log "$SUB_ID" --apple-id "$APPLE_ID" \ | |
| --password "$APPLE_APP_SPECIFIC_PASSWORD" --team-id "$TEAM_ID" \ | |
| "$RUNNER_TEMP/notary-log-$SUB_ID.json" || true | |
| cat "$RUNNER_TEMP/notary-log-$SUB_ID.json" || true | |
| echo | |
| done | |
| - name: Smoke test bundled backend (Windows) | |
| if: runner.os == 'Windows' | |
| shell: bash | |
| env: | |
| # Skip the live Playwright browser download — see the macOS smoke | |
| # test step for the full rationale. | |
| PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: '1' | |
| run: | | |
| mkdir -p "$RUNNER_TEMP/awd-home" | |
| desktop/bundled/win-x64/jre/bin/java.exe \ | |
| -Duser.home="$RUNNER_TEMP/awd-home" \ | |
| -jar desktop/bundled/win-x64/backend.jar \ | |
| --spring.profiles.active=desktop --server.port=9696 \ | |
| > "$RUNNER_TEMP/backend-smoke.log" 2>&1 & | |
| BACK_PID=$! | |
| for i in $(seq 1 60); do | |
| sleep 2 | |
| if curl -sf -o /dev/null http://127.0.0.1:9696/api/admin/wizard; then | |
| echo "backend up after ~$((i*2))s" | |
| curl -s http://127.0.0.1:9696/api/admin/wizard; echo | |
| kill $BACK_PID | |
| exit 0 | |
| fi | |
| done | |
| echo "backend failed to start within 120s" | |
| tail -80 "$RUNNER_TEMP/backend-smoke.log" | |
| kill $BACK_PID 2>/dev/null || true | |
| exit 1 | |
| - name: Smoke test bundled pptx-service (Windows) | |
| if: runner.os == 'Windows' | |
| shell: bash | |
| run: | | |
| export PPTX_DATA_DIR="$RUNNER_TEMP/pptx-data" | |
| mkdir -p "$PPTX_DATA_DIR" | |
| APP="$PWD/desktop/bundled/win-x64/pysvc/pptx-service/app" | |
| PY="$PWD/desktop/bundled/win-x64/python/python.exe" | |
| export PYTHONPATH="$PWD/desktop/bundled/win-x64/pysvc/pptx-service/lib" | |
| (cd "$APP" && "$PY" -m alembic -c alembic.ini upgrade head) | |
| PORT=5099 BACKEND_PORT=5099 FLASK_ENV=production "$PY" "$APP/app.py" > "$RUNNER_TEMP/pptx-smoke.log" 2>&1 & | |
| PPTX_PID=$! | |
| for i in $(seq 1 30); do | |
| sleep 2 | |
| if curl -sf http://127.0.0.1:5099/health; then | |
| echo; echo "pptx-service up after ~$((i*2))s" | |
| kill $PPTX_PID | |
| exit 0 | |
| fi | |
| done | |
| echo "pptx-service failed to start within 60s" | |
| tail -80 "$RUNNER_TEMP/pptx-smoke.log" | |
| kill $PPTX_PID 2>/dev/null || true | |
| exit 1 | |
| - name: Smoke test bundled mineru-service (Windows) | |
| if: runner.os == 'Windows' | |
| shell: bash | |
| run: | | |
| export MDIR="$RUNNER_TEMP/mineru-data" | |
| mkdir -p "$MDIR" | |
| PY="$PWD/desktop/bundled/win-x64/python/python.exe" | |
| export PYTHONPATH="$PWD/desktop/bundled/win-x64/pysvc/mineru-service/lib" | |
| MINERU_DEVICE_MODE=cpu MINERU_MODEL_SOURCE=modelscope \ | |
| MODELSCOPE_CACHE="$MDIR" HF_HOME="$MDIR/hf" MINERU_TOOLS_CONFIG_JSON="$MDIR/mineru.json" \ | |
| "$PY" -m mineru.cli.fast_api --host 127.0.0.1 --port 8098 > "$RUNNER_TEMP/mineru-smoke.log" 2>&1 & | |
| MINERU_PID=$! | |
| for i in $(seq 1 30); do | |
| sleep 2 | |
| if curl -sf -o /dev/null http://127.0.0.1:8098/docs; then | |
| echo "mineru-service up after ~$((i*2))s" | |
| kill $MINERU_PID | |
| exit 0 | |
| fi | |
| done | |
| echo "mineru-service failed to start within 60s" | |
| tail -80 "$RUNNER_TEMP/mineru-smoke.log" | |
| kill $MINERU_PID 2>/dev/null || true | |
| exit 1 | |
| - name: Smoke test bundled kokoro-service (Windows) | |
| if: runner.os == 'Windows' | |
| shell: bash | |
| run: | | |
| PY="$PWD/desktop/bundled/win-x64/python/python.exe" | |
| APP="$PWD/desktop/bundled/win-x64/pysvc/kokoro-service/app" | |
| export PYTHONPATH="$PWD/desktop/bundled/win-x64/pysvc/kokoro-service/lib" | |
| PORT=8881 "$PY" "$APP/app.py" > "$RUNNER_TEMP/kokoro-smoke.log" 2>&1 & | |
| KOKORO_PID=$! | |
| for i in $(seq 1 20); do | |
| sleep 2 | |
| if curl -sf http://127.0.0.1:8881/health && curl -sf http://127.0.0.1:8881/v1/audio/voices | grep -q zf_001; then | |
| echo; echo "kokoro-service up after ~$((i*2))s" | |
| kill $KOKORO_PID | |
| exit 0 | |
| fi | |
| done | |
| echo "kokoro-service failed to start within 40s" | |
| tail -60 "$RUNNER_TEMP/kokoro-smoke.log" | |
| kill $KOKORO_PID 2>/dev/null || true | |
| exit 1 | |
| - name: Pack pysvc archive (Windows) | |
| if: runner.os == 'Windows' | |
| shell: bash | |
| run: | | |
| # 与 macOS 同构:安装包只带一个 pysvc.tar.gz(文件数大降、安装更快),首启解压 | |
| node desktop/scripts/pack-pysvc.js --bundle desktop/bundled/win-x64 | |
| - name: Package installers (Windows, unsigned) | |
| if: runner.os == 'Windows' | |
| working-directory: desktop | |
| env: | |
| # Windows code signing not set up yet (issue #12) | |
| CSC_IDENTITY_AUTO_DISCOVERY: 'false' | |
| run: npx electron-builder --publish never | |
| - uses: actions/upload-artifact@v4 | |
| with: | |
| name: aiworkdeck-${{ matrix.os }} | |
| path: | | |
| desktop/release/*.dmg | |
| desktop/release/*.exe | |
| if-no-files-found: error | |
| - name: Attach to GitHub Release | |
| if: startsWith(github.ref, 'refs/tags/v') | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| files: | | |
| desktop/release/*.dmg | |
| desktop/release/*.exe | |
| # Auto-generate a "What's Changed" changelog from merged PRs since the | |
| # last tag, appended below the bilingual body template. Without this a | |
| # tag build creates a Release with EMPTY notes (had to `gh release edit` | |
| # by hand every time). The template is version-agnostic so it stays | |
| # correct on every release; per-release highlights can still be edited in. | |
| generate_release_notes: true | |
| body: | | |
| ## AI Workdeck — 律师的 AI 原生工作台 / The lawyer's AI-native workstation | |
| **中文** · 桌面版「双击即用」:安装包已捆绑后端与精简 JRE,无需预装 PostgreSQL / Java。首次启动有向导,配置一项 AI 提供商即可解锁核心功能;可选「本地 Ollama」实现数据不出本机。 | |
| **English** · Desktop, double-click to run: the installer bundles the backend and a trimmed JRE — no PostgreSQL or Java setup. A first-run wizard gets you started; configure one AI provider to unlock core features. Pick local Ollama to keep all data on-device. | |
| ### 下载 / Downloads | |
| - **macOS · Apple Silicon (M 系列)** — `AI Workdeck-<ver>-arm64.dmg`(已签名公证 / signed & notarized) | |
| - **Windows · x64** — `AI Workdeck Setup <ver>.exe`(暂未签名,首次运行 SmartScreen 提示属正常 / not yet code-signed; a SmartScreen prompt on first run is expected) | |
| > 自本版本起不再提供 Intel Mac 安装包(依赖生态已停发 x86_64 预编译组件)。/ Intel mac builds are discontinued as of this release. | |
| 官网 / Website: https://www.aiworkdeck.com | |
| --- |