diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index ec6b88a6..edcb1184 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -20,15 +20,42 @@ npm run lint # Check lint errors npm run check # Run all checks (CI) ``` -Pre-commit hooks via Husky will run Biome automatically on staged files. +Git hooks via Husky run automatically: +- **Pre-commit**: Biome (format/lint) + TypeScript type check +- **Pre-push**: Unit tests For a better experience, install the [Biome VS Code extension](https://marketplace.visualstudio.com/items?itemName=biomejs.biome) for real-time linting and format-on-save. +## Testing + +Run tests before submitting PRs: + +```bash +npm run test # Unit tests (Vitest) +npm run test:e2e # E2E tests (Playwright) +``` + +E2E tests use mocked API responses - no AI provider needed. Tests are in `tests/e2e/`. + +To run a specific test file: +```bash +npx playwright test tests/e2e/diagram-generation.spec.ts +``` + +To run tests with UI mode: +```bash +npx playwright test --ui +``` + ## Pull Requests 1. Create a feature branch -2. Make changes and ensure `npm run check` passes -3. Submit PR against `main` with a clear description +2. Make changes (pre-commit runs lint + type check automatically) +3. Run E2E tests with `npm run test:e2e` +4. Push (pre-push runs unit tests automatically) +5. Submit PR against `main` with a clear description + +CI will run the full test suite on your PR. ## Issues diff --git a/.github/renovate.json b/.github/renovate.json index 8f2861ed..437ea5d6 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -1,7 +1,7 @@ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": ["config:recommended"], - "schedule": ["after 10am on saturday"], + "schedule": ["after 10am on the first day of the month"], "timezone": "Asia/Tokyo", "packageRules": [ { @@ -13,6 +13,7 @@ { "matchUpdateTypes": ["major"], "matchPackagePatterns": ["*"], + "groupName": "major dependencies", "automerge": false }, { diff --git a/.github/workflows/electron-release.yml b/.github/workflows/electron-release.yml index e6e57f0a..9cb15ac6 100644 --- a/.github/workflows/electron-release.yml +++ b/.github/workflows/electron-release.yml @@ -11,7 +11,8 @@ on: required: false jobs: - build: + # Mac and Linux: Build and publish directly (no signing needed) + build-mac-linux: permissions: contents: write strategy: @@ -20,13 +21,9 @@ jobs: include: - os: macos-latest platform: mac - - os: windows-latest - platform: win - os: ubuntu-latest platform: linux - runs-on: ${{ matrix.os }} - steps: - name: Checkout code uses: actions/checkout@v6 @@ -40,7 +37,58 @@ jobs: - name: Install dependencies run: npm install - - name: Build and publish Electron app + - name: Build and publish run: npm run dist:${{ matrix.platform }} env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + # Windows: Build, sign with SignPath, then publish + build-windows: + permissions: + contents: write + runs-on: windows-latest + steps: + - name: Checkout code + uses: actions/checkout@v6 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: 24 + cache: "npm" + + - name: Install dependencies + run: npm install + + # Build WITHOUT publishing + - name: Build Windows app + run: npm run dist:win:build + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Upload unsigned artifacts for signing + uses: actions/upload-artifact@v6 + id: upload-unsigned + with: + name: windows-unsigned + path: release/*.exe + retention-days: 1 + + - name: Sign with SignPath + uses: signpath/github-action-submit-signing-request@v2 + with: + api-token: ${{ secrets.SIGNPATH_API_TOKEN }} + organization-id: '880a211d-2cd3-4e7b-8d04-3d1f8eb39df5' + project-slug: 'next-ai-draw-io' + signing-policy-slug: 'test-signing' + artifact-configuration-slug: 'windows-exe' + github-artifact-id: ${{ steps.upload-unsigned.outputs.artifact-id }} + wait-for-completion: true + output-artifact-directory: release-signed + + - name: Upload signed artifacts to release + uses: softprops/action-gh-release@v2 + with: + files: release-signed/*.exe + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 00000000..52428f18 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,75 @@ +name: Test + +on: + pull_request: + branches: [main] + push: + branches: [main] + +jobs: + lint-and-unit: + name: Lint & Unit Tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: "20" + cache: "npm" + + - name: Install dependencies + run: npm ci + + - name: Run lint + run: npm run check + + - name: Run unit tests + run: npm run test -- --run + + e2e: + name: E2E Tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Setup Node.js + uses: actions/setup-node@v6 + with: + node-version: "20" + cache: "npm" + + - name: Install dependencies + run: npm ci + + - name: Cache Playwright browsers + uses: actions/cache@v5 + id: playwright-cache + with: + path: ~/.cache/ms-playwright + key: playwright-${{ runner.os }}-${{ hashFiles('**/package-lock.json') }} + + - name: Install Playwright browsers + if: steps.playwright-cache.outputs.cache-hit != 'true' + run: npx playwright install chromium --with-deps + + - name: Install Playwright deps (cached) + if: steps.playwright-cache.outputs.cache-hit == 'true' + run: npx playwright install-deps chromium + + - name: Build app + run: npm run build + + - name: Run E2E tests + run: npm run test:e2e + env: + CI: true + + - name: Upload test results + uses: actions/upload-artifact@v6 + if: always() + with: + name: playwright-report + path: playwright-report/ + retention-days: 7 diff --git a/.gitignore b/.gitignore index e0596a24..ef02da91 100644 --- a/.gitignore +++ b/.gitignore @@ -14,6 +14,8 @@ packages/*/dist # testing /coverage +/playwright-report/ +/test-results/ # next.js /.next/ @@ -65,4 +67,5 @@ CLAUDE.md .spec-workflow # edgeone -.edgeone \ No newline at end of file +.edgeone +opencode.json \ No newline at end of file diff --git a/.husky/pre-commit b/.husky/pre-commit index 2312dc58..ef9088d0 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1 +1,2 @@ npx lint-staged +npx tsc --noEmit diff --git a/.husky/pre-push b/.husky/pre-push new file mode 100644 index 00000000..2674c5f2 --- /dev/null +++ b/.husky/pre-push @@ -0,0 +1,4 @@ +# Skip if node_modules not installed (e.g., on EC2 push server) +if [ -d "node_modules" ]; then + npm run test -- --run +fi diff --git a/Dockerfile b/Dockerfile index fb674006..a4af9806 100644 --- a/Dockerfile +++ b/Dockerfile @@ -30,6 +30,10 @@ ENV NEXT_PUBLIC_DRAWIO_BASE_URL=${NEXT_PUBLIC_DRAWIO_BASE_URL} ARG NEXT_PUBLIC_SHOW_ABOUT_AND_NOTICE=false ENV NEXT_PUBLIC_SHOW_ABOUT_AND_NOTICE=${NEXT_PUBLIC_SHOW_ABOUT_AND_NOTICE} +# Build-time argument for subdirectory deployment (e.g., /nextaidrawio) +ARG NEXT_PUBLIC_BASE_PATH="" +ENV NEXT_PUBLIC_BASE_PATH=${NEXT_PUBLIC_BASE_PATH} + # Build Next.js application (standalone mode) RUN npm run build diff --git a/README.md b/README.md index 6d7b405b..7db249aa 100644 --- a/README.md +++ b/README.md @@ -40,11 +40,12 @@ https://github.com/user-attachments/assets/9d60a3e8-4a1c-4b5e-acbb-26af2d3eabd1 - [Installation](#installation) - [Deployment](#deployment) - [Deploy to EdgeOne Pages](#deploy-to-edgeone-pages) - - [Deploy on Vercel (Recommended)](#deploy-on-vercel-recommended) + - [Deploy on Vercel](#deploy-on-vercel) - [Deploy on Cloudflare Workers](#deploy-on-cloudflare-workers) - [Multi-Provider Support](#multi-provider-support) - [How It Works](#how-it-works) - [Support \& Contact](#support--contact) + - [FAQ](#faq) - [Star History](#star-history) ## Examples @@ -185,7 +186,7 @@ Check out the [Tencent EdgeOne Pages documentation](https://pages.edgeone.ai/doc Additionally, deploying through Tencent EdgeOne Pages will also grant you a [daily free quota for DeepSeek models](https://pages.edgeone.ai/document/edge-ai). -### Deploy on Vercel (Recommended) +### Deploy on Vercel [](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2FDayuanJiang%2Fnext-ai-draw-io) @@ -211,6 +212,7 @@ See the [Next.js deployment documentation](https://nextjs.org/docs/app/building- - OpenRouter - DeepSeek - SiliconFlow +- ModelScope - SGLang - Vercel AI Gateway @@ -245,6 +247,10 @@ For support or inquiries, please open an issue on the GitHub repository or conta - Email: me[at]jiang.jp +## FAQ + +See [FAQ](./docs/en/FAQ.md) for common issues and solutions. + ## Star History [](https://www.star-history.com/#DayuanJiang/next-ai-draw-io&type=date&legend=top-left) diff --git a/app/[lang]/about/cn/page.tsx b/app/[lang]/about/cn/page.tsx index 591df695..e88a8add 100644 --- a/app/[lang]/about/cn/page.tsx +++ b/app/[lang]/about/cn/page.tsx @@ -10,18 +10,7 @@ export const metadata: Metadata = { keywords: ["AI图表", "draw.io", "AWS架构", "GCP图表", "Azure图表", "LLM"], } -function formatNumber(num: number): string { - if (num >= 1000) { - return `${num / 1000}k` - } - return num.toString() -} - export default function AboutCN() { - const dailyRequestLimit = Number(process.env.DAILY_REQUEST_LIMIT) || 20 - const dailyTokenLimit = Number(process.env.DAILY_TOKEN_LIMIT) || 500000 - const tpmLimit = Number(process.env.TPM_LIMIT) || 50000 - return (
- 当前使用限制: -
-- {formatNumber(dailyRequestLimit)} -
-- 请求/天 -
-- {formatNumber(dailyTokenLimit)} -
-- Token/天 -
-- {formatNumber(tpmLimit)} -
-- Token/分钟 -
-
注意:claude-sonnet-4-5{" "}
diff --git a/app/[lang]/about/ja/page.tsx b/app/[lang]/about/ja/page.tsx
index 4dd22d64..dbbabaf2 100644
--- a/app/[lang]/about/ja/page.tsx
+++ b/app/[lang]/about/ja/page.tsx
@@ -17,18 +17,7 @@ export const metadata: Metadata = {
],
}
-function formatNumber(num: number): string {
- if (num >= 1000) {
- return `${num / 1000}k`
- }
- return num.toString()
-}
-
export default function AboutJA() {
- const dailyRequestLimit = Number(process.env.DAILY_REQUEST_LIMIT) || 20
- const dailyTokenLimit = Number(process.env.DAILY_TOKEN_LIMIT) || 500000
- const tpmLimit = Number(process.env.TPM_LIMIT) || 50000
-
return (
- 現在の使用制限: -
-- {formatNumber(dailyRequestLimit)} -
-- リクエスト/日 -
-- {formatNumber(dailyTokenLimit)} -
-- トークン/日 -
-- {formatNumber(tpmLimit)} -
-- トークン/分 -
-
注:claude-sonnet-4-5
diff --git a/app/[lang]/about/page.tsx b/app/[lang]/about/page.tsx
index d5240fdc..2750b515 100644
--- a/app/[lang]/about/page.tsx
+++ b/app/[lang]/about/page.tsx
@@ -17,18 +17,7 @@ export const metadata: Metadata = {
],
}
-function formatNumber(num: number): string {
- if (num >= 1000) {
- return `${num / 1000}k`
- }
- return num.toString()
-}
-
export default function About() {
- const dailyRequestLimit = Number(process.env.DAILY_REQUEST_LIMIT) || 20
- const dailyTokenLimit = Number(process.env.DAILY_TOKEN_LIMIT) || 500000
- const tpmLimit = Number(process.env.TPM_LIMIT) || 50000
-
return (
- Please note the current usage limits: -
-- {formatNumber(dailyRequestLimit)} -
-- requests/day -
-- {formatNumber(dailyTokenLimit)} -
-- tokens/day -
-- {formatNumber(tpmLimit)} -
-- tokens/min -
-
Note that claude-sonnet-4-5 has trained on
diff --git a/app/[lang]/page.tsx b/app/[lang]/page.tsx
index ac06a5f8..30fa1346 100644
--- a/app/[lang]/page.tsx
+++ b/app/[lang]/page.tsx
@@ -4,7 +4,6 @@ import { Suspense, useCallback, useEffect, useRef, useState } from "react"
import { DrawIoEmbed } from "react-drawio"
import type { ImperativePanelHandle } from "react-resizable-panels"
import ChatPanel from "@/components/chat-panel"
-import { STORAGE_CLOSE_PROTECTION_KEY } from "@/components/settings-dialog"
import {
ResizableHandle,
ResizablePanel,
@@ -34,7 +33,6 @@ export default function Home() {
const [darkMode, setDarkMode] = useState(false)
const [isLoaded, setIsLoaded] = useState(false)
const [isDrawioReady, setIsDrawioReady] = useState(false)
- const [closeProtection, setCloseProtection] = useState(false)
const chatPanelRef = useRef
+ urlData?: Map