Skip to content

feat: add ModelScope provider, rich metadata & comprehensive infra overhaul#19

Merged
WuMingDao merged 21 commits intomainfrom
dev
Dec 13, 2025
Merged

feat: add ModelScope provider, rich metadata & comprehensive infra overhaul#19
WuMingDao merged 21 commits intomainfrom
dev

Conversation

@WuMingDao
Copy link
Copy Markdown
Owner

@WuMingDao WuMingDao commented Dec 13, 2025

Summary by CodeRabbit

  • New Features

    • Added ModelScope provider, richer image metadata in responses, and standardized API error types.
    • New repository templates and automation for PRs, issues, previews, releases, deployments, Docker, dependency updates, and security checks.
  • Bug Fixes

    • Improved and unified error handling, validation, timeouts, and request size limits.
  • Documentation

    • Updated API docs, contributing guide, and changelog to reflect new response format and testing/PR workflows.
  • Tests

    • Large expansion of tests across API, providers, utilities, and web.

✏️ Tip: You can customize this high-level summary in your review settings.

WuMingDao and others added 19 commits December 13, 2025 11:40
- Add ApiError class and ApiErrorCode enum in shared package
- Add Errors helper with factory methods for common error types
- Support error codes: AUTH_REQUIRED, AUTH_INVALID, AUTH_EXPIRED,
  RATE_LIMITED, QUOTA_EXCEEDED, INVALID_*, PROVIDER_ERROR, TIMEOUT
- Update all providers (Gitee, HuggingFace, ModelScope) to throw typed errors
- Parse provider-specific error responses to detect auth/quota/rate limit issues
- Add sendError helper and toApiError converter in app.ts
- Update frontend API client to parse error codes and show user-friendly messages
- Error response format: { error: string, code: ApiErrorCode, details?: {...} }

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Add global error handling with onError and notFound handlers
- Add request logging middleware with request ID tracing
- Add security headers (X-Content-Type-Options, X-Frame-Options, etc.)
- Add timeout middleware (120s for generation endpoints)
- Add body limit middleware (50KB)
- Extract formatDimensions/formatDuration to utils/format.ts
- Extract Gradio API helpers to utils/gradio.ts
- Optimize CORS by pre-creating middleware instance
- Update prompt max length from 10000 to 4000 characters

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Add unit tests for utils (formatDimensions, formatDuration)
- Add unit tests for error-handler middleware
- Add Provider tests with mocked fetch (Gitee, HuggingFace)
- Add route integration tests using Hono's app.request()
- Add vitest.config.ts for API package
- Upgrade vitest to v4.0.15 in root package.json
- Remove duplicate vitest from api package (monorepo best practice)

107 tests covering validation, auth, error handling, and API routes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Add vitest config with jsdom environment for web package
- Add tests for lib/api.ts (error messages, API client)
- Add tests for lib/utils.ts (cn utility, URL detection)
- Add tests for lib/constants.ts (settings persistence, providers)
- Add test setup with localStorage mock

55 tests covering core frontend utilities

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Update response format to match actual code (imageDetails)
- Add error response format with code and details
- Add ModelScope to Chinese README
- Update CHANGELOG with v0.5.1

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Add section 6: Running Tests with commands and structure
- Add section 8: Submitting a PR with pre-check commands
- Add section 9: Commit Message Convention (Conventional Commits)
- Document CI pipeline requirements

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Fix import order in vitest.config.ts
- Fix type assertion in constants.test.ts
- Format gitee.test.ts

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Run tests with coverage (pnpm test:coverage)
- Upload coverage to Codecov on main branch push
- Keep existing lint, build, typecheck jobs

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Deploy PR previews to Cloudflare Pages
- Comment preview URL on PR automatically
- Cancel in-progress deployments for same branch

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Manual trigger with version bump selection (patch/minor/major)
- Auto-generate changelog from conventional commits
- Create GitHub release with categorized changes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Label PRs based on changed files (api, web, shared, docs, ci)
- Label PRs based on title prefix (feat, fix, docs, test, etc.)
- Add labeler configuration for file patterns

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Mark issues stale after 30 days, close after 7 more
- Mark PRs stale after 14 days, close after 7 more
- Exempt pinned, security, and help-wanted labels
- Run daily at midnight UTC

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- CodeQL analysis for JavaScript/TypeScript
- Dependency review on PRs (block high severity)
- Run on push to main and weekly schedule

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Weekly dependency updates (Monday 9am Shanghai time)
- Separate configs for root, api, web packages
- Group dev dependencies and React/UI packages
- Auto-label PRs with dependencies and scope

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- PR template with type/scope checklist
- Bug report template with fix(scope) title format
- Feature request template with feat(scope) title format
- Disable blank issues, add docs/discussions links

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Deploy to Cloudflare Pages on main push
- Deploy API to Cloudflare Workers (path filtered)
- Docker build and push to GHCR on release

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Fix github.sha.substring() syntax in preview.yml
- Remove explicit pnpm version (use packageManager from package.json)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Add type definitions for API response objects in app.test.ts
- Add required fields (model, width, height) to provider tests
- Use defaultRequest pattern for cleaner test code

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
Restrict CORS on the development server to prevent malicious websites
from reading source files. This addresses the esbuild CVE pattern where
`Access-Control-Allow-Origin: *` allows any site to fetch dev server files.

Note: This only affects `vite dev`, not production builds or deployments.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented Dec 13, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
zenith-image-generator-web Ready Ready Preview, Comment Dec 13, 2025 8:24am

@netlify
Copy link
Copy Markdown

netlify Bot commented Dec 13, 2025

Deploy Preview for frolicking-blancmange-ef6637 canceled.

Name Link
🔨 Latest commit f0bae85
🔍 Latest deploy log https://app.netlify.com/projects/frolicking-blancmange-ef6637/deploys/693d22bee070aa0008d1ef53

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Dec 13, 2025

Walkthrough

Adds comprehensive GitHub CI/CD and repository metadata; introduces centralized API middleware (error handling, logging, security, timeouts, body-size limits, request IDs); standardizes provider error parsing and shared error types; adds Gradio SSE/queue and formatting utilities; extensive tests for API and web; web client error mapping; and documentation updates reflecting the new response shape and contributor workflow.

Changes

Cohort / File(s) Change Summary
GitHub workflows & config
​.github/workflows/*, ​.github/ISSUE_TEMPLATE/*, ​.github/labeler.yml, ​.github/dependabot.yml, ​.github/PULL_REQUEST_TEMPLATE.md, .gitignore
Add CI, preview, deploy (Cloudflare Pages/Workers), Docker build, release, security, stale, and labeler workflows; issue/PR templates, Dependabot and labeler config; stop ignoring .github.
API middleware
apps/api/src/middleware/*, apps/api/src/middleware/index.ts
New middleware: global error normalization/handler, not-found handler, requestId, requestLogger, security headers, timeout, bodyLimit; consolidated exports.
API core & routes
apps/api/src/app.ts, apps/api/package.json, apps/api/vitest.config.ts, apps/api/src/__tests__/*, apps/api/src/__tests__/setup.ts
Wire middleware into app, standardize error responses, improve health route, enforce body/time limits, add Vitest config and extensive integration tests and test setup.
Providers & provider tests
apps/api/src/providers/*.ts, apps/api/src/providers/__tests__/*
Centralize provider error parsing (Gitee/HuggingFace/ModelScope), enforce auth handling, funnel errors through shared Errors utilities; export modelscopeProvider; add comprehensive provider unit tests.
Gradio & utils
apps/api/src/utils/*, apps/api/src/utils/index.ts, apps/api/src/utils/__tests__/format.test.ts
Add SSE parsing (extractCompleteEventData), callGradioApi helper, formatDimensions and formatDuration, tests, and re-export utilities.
Shared error types & validation
packages/shared/src/types/error.ts, packages/shared/src/types/index.ts, packages/shared/src/utils/validation.{ts,test}, packages/shared/src/constants/models.ts
Add ApiErrorCode, ErrorCodeToStatus, ApiError class and Errors factory helpers; expose via types barrel; lower prompt max length to 4000; minor model config formatting.
Web client & tests
apps/web/src/lib/api.ts, apps/web/src/lib/__tests__/*, apps/web/src/__tests__/setup.ts, apps/web/package.json, apps/web/vitest.config.ts, apps/web/vite.config.ts
Add ApiErrorInfo and getErrorMessage, extend ApiResponse to include errorInfo, rework client error parsing; add tests for API client, constants, utils, test setup, jsdom & vitest deps, and a dev server CORS config.
Docs & metadata
docs/*, README.zh.md, package.json, docs/CHANGELOG.md
Update API docs to nested imageDetails response and error codes table; expand CONTRIBUTING with testing/PR guidance; add changelog entry; update Chinese README provider list; bump vitest-related deps and add wrangler; update deploy script.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant Client
    participant ReqID as RequestId MW
    participant Logger as RequestLogger MW
    participant Security as SecurityHeaders MW
    participant BodyLimit as BodyLimit MW
    participant Timeout as Timeout MW
    participant Handler as Route Handler
    participant Provider as Provider (Gitee/HF/ModelScope)
    participant Ext as External API
    participant ErrorH as ErrorHandler MW

    Client->>ReqID: HTTP request (e.g., POST /api/generate)
    ReqID->>Logger: next()
    Logger->>Security: next()
    Security->>BodyLimit: next()
    BodyLimit->>Timeout: next()
    Timeout->>Handler: next() (start timeout race)
    Handler->>Provider: validate & call provider
    Provider->>Ext: external API request
    alt external success
        Ext-->>Provider: image + metadata
        Provider-->>Handler: normalized result
        Handler-->>Client: 200 + { imageDetails }
    else external error
        Ext-->>Provider: error response
        Provider-->>Handler: throw ApiError
        Handler->>ErrorH: propagate
        ErrorH-->>Client: JSON error (status from ErrorCodeToStatus)
    end
    alt timeout triggered
        Timeout->>ErrorH: Errors.timeout()
        ErrorH-->>Client: 504 timeout response
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Areas needing extra attention:

  • Middleware ordering and interactions (apps/api/src/app.ts, apps/api/src/middleware/*).
  • Error code → HTTP status mappings and consistent usage across providers (packages/shared/src/types/error.ts, apps/api/src/providers/*).
  • Gradio SSE parsing and polling robustness (apps/api/src/utils/gradio.ts).
  • New and extensive tests for flakiness and realistic mocking (apps/api/src/__tests__/*, apps/web/src/lib/__tests__/*).
  • Web client API shape changes (apps/web/src/lib/api.ts) and downstream consumers expecting imageDetails.

Possibly related PRs

Poem

🐰
Pipelines hum and tests take flight,
Middleware keeps each call polite,
Providers speak in tidy tones,
Errors mapped to numbered bones,
I hop, I cheer—this code's just right. 🎉

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 88.89% which is sufficient. The required threshold is 80.00%.
Title check ✅ Passed The PR title accurately captures the three main themes of this changeset: adding ModelScope provider support, introducing rich metadata structures (error handling, image details), and comprehensive infrastructure improvements (GitHub Actions, testing, Dependabot, issue templates).
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dev

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented Dec 13, 2025

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
zenith-image-generator-api f0bae85 Commit Preview URL Dec 13 2025, 08:24 AM

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 14

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
docs/en/API.md (1)

112-182: Clarify request-field naming + make error-code table explicitly complete or explicitly non-exhaustive.
Right now the request example is snake_case (negative_prompt, num_inference_steps) while the parameters table is mostly camelCase (negativePrompt, steps). Consider adding a short “Accepted field aliases” note and standardizing the table to one canonical naming scheme. Also, either add the remaining error codes you may return (e.g., AUTH_EXPIRED, INVALID_PROVIDER, INVALID_MODEL, INVALID_PARAMS, GENERATION_FAILED, UNKNOWN) or call the table “common error codes” / “non-exhaustive”.

docs/zh/API.md (1)

112-182: 同样建议:明确请求字段命名/别名,并说明错误码表是否“完整”。
示例里是 snake_case(negative_prompt, num_inference_steps),参数表里主要是 camelCase(negativePrompt, steps)。建议增加“支持的字段别名/规范字段名”说明并统一参数表口径;同时把可能返回的错误码补全,或注明该表为“常见/非完整列表”。

🧹 Nitpick comments (12)
docs/CHANGELOG.md (1)

3-21: Avoid hard-coding exact test counts in the changelog (likely to drift).

“162 tests across 9 files” will become inaccurate quickly as tests evolve; consider wording like “Added comprehensive Vitest test coverage across API and web packages” (or keep the counts only if they’re intended to reflect the release artifact at tag time).

.github/labeler.yml (1)

24-37: Consider widening ci/testing glob coverage (root-only YAML, TSX tests).

If the intent is to label YAML anywhere (not just repo-root), use **/*.yml / **/*.yaml. Similarly, if you have React tests, consider including **/*.test.tsx / **/*.spec.tsx.

apps/api/src/middleware/logger.ts (1)

11-26: Consider structured logging for production and document middleware order dependency.

The current implementation has a few areas for improvement:

  1. Middleware order dependency: The logger retrieves requestId using c.get('requestId'), which assumes the request-id middleware runs first. This dependency should be documented in the JSDoc comment.

  2. Production logging: Using console.log with emoji indicators works for development but isn't ideal for production. Consider:

    • Using structured JSON logging for production environments
    • Conditionally enabling emoji indicators based on environment
    • Using proper log levels (info, warn, error)
  3. Error context: If an error occurs during request processing, the logger still logs the response, but additional error context from the error handler might be valuable.

Example improvement:

 /**
  * Simple request logger middleware
- * Logs method, path, status, and duration
+ * Logs method, path, status, and duration
+ * Note: Must run after request-id middleware to include requestId
  */
 export const requestLogger: MiddlewareHandler = async (c, next) => {
   const start = Date.now()
   const method = c.req.method
   const path = c.req.path

   await next()

   const duration = Date.now() - start
   const status = c.res.status
   const requestId = c.get('requestId') || '-'

-  // Color-coded status for development
-  const statusColor = status >= 500 ? '❌' : status >= 400 ? '⚠️' : '✅'
-
-  console.log(`${statusColor} [${requestId}] ${method} ${path} ${status} ${duration}ms`)
+  // Use structured logging for production, emoji for dev
+  const isDev = process.env.NODE_ENV === 'development'
+  
+  if (isDev) {
+    const statusColor = status >= 500 ? '❌' : status >= 400 ? '⚠️' : '✅'
+    console.log(`${statusColor} [${requestId}] ${method} ${path} ${status} ${duration}ms`)
+  } else {
+    console.log(JSON.stringify({
+      requestId,
+      method,
+      path,
+      status,
+      duration,
+      timestamp: new Date().toISOString()
+    }))
+  }
 }
.github/workflows/preview.yml (1)

63-73: Consider pagination for repositories with many comments.

The script uses listComments without pagination handling. For repositories with many PR comments, this could miss the bot comment if it's not in the first page of results.

Consider adding pagination or limiting the search:

-            const { data: comments } = await github.rest.issues.listComments({
+            const { data: comments } = await github.rest.issues.listComments({
               owner: context.repo.owner,
               repo: context.repo.repo,
               issue_number: context.issue.number,
+              per_page: 100
             });

Alternatively, you could search from the end since the bot comment is likely recent, or use a more specific marker in the comment body to make searching more efficient.

apps/web/vitest.config.ts (1)

18-23: Consider including React components in coverage.

The coverage configuration only includes src/lib/**/*.ts and src/hooks/**/*.ts, which excludes React components from coverage reporting.

If you have components in src/components/**/*.tsx that contain business logic, consider adding them to coverage:

     coverage: {
       provider: 'v8',
       reporter: ['text', 'json', 'html'],
-      include: ['src/lib/**/*.ts', 'src/hooks/**/*.ts'],
+      include: ['src/lib/**/*.ts', 'src/hooks/**/*.ts', 'src/components/**/*.tsx'],
       exclude: ['**/*.test.ts', '**/*.spec.ts'],
     },

If components are purely presentational or tested via integration tests, the current configuration is appropriate.

.github/workflows/ci.yml (2)

57-63: Hardcoded coverage paths may break if output locations change.

The coverage upload step specifies exact file paths:

files: ./apps/api/coverage/coverage-final.json,./apps/web/coverage/coverage-final.json

If the coverage output directory changes or additional packages are added, this will fail silently.

Consider using a glob pattern or script to discover coverage files:

       - name: Upload coverage to Codecov
         uses: codecov/codecov-action@v4
         if: github.event_name == 'push' && github.ref == 'refs/heads/main'
         with:
           token: ${{ secrets.CODECOV_TOKEN }}
-          files: ./apps/api/coverage/coverage-final.json,./apps/web/coverage/coverage-final.json
+          directory: ./
+          flags: unittests
           fail_ci_if_error: false

The codecov-action will automatically discover all coverage files when directory is specified.


21-89: Consider build artifact caching between jobs.

Both ci and typecheck jobs install dependencies and build the shared package independently. This duplicates work and increases CI time.

You could optimize by:

  1. Creating a setup job that builds shared and caches it
  2. Using workflow artifacts to share the build between jobs

Example:

jobs:
  setup:
    name: Setup & Build Shared
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4
      - name: Setup pnpm
        uses: pnpm/action-setup@v4
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'
      - name: Install dependencies
        run: pnpm install --frozen-lockfile
      - name: Build shared package
        run: pnpm build:shared
      - name: Cache build artifacts
        uses: actions/cache/save@v4
        with:
          path: |
            packages/shared/dist
            node_modules
          key: build-${{ github.sha }}

  ci:
    needs: setup
    # ... restore cache and skip shared build
  
  typecheck:
    needs: setup
    # ... restore cache and skip shared build

However, this adds complexity, so only implement if CI time is a concern.

apps/api/vitest.config.ts (1)

3-17: Consider adding deterministic test defaults (timeouts / mock reset).
Config is solid; adding testTimeout and clearMocks (or restoreMocks) can reduce flakes and cross-test coupling.

.github/workflows/security.yml (1)

31-48: Pin GitHub Actions to commit SHAs (supply-chain hardening).
Using floating tags (e.g. @v3, @v4) is convenient but less secure than SHA pinning.

Also applies to: 54-62

.github/workflows/labeler.yml (1)

21-50: Pin actions + tighten conventional-commit title matching.
Consider SHA pinning for actions/labeler / actions/github-script, and use a small regex (case-insensitive) for ^(feat|fix|docs|test|refactor|chore|perf)(\\(.+\\))?(!)?: to reduce false positives.

apps/api/src/middleware/error-handler.ts (1)

48-56: Consider aligning with the ApiErrorCode enum.

The notFoundHandler uses the literal string 'NOT_FOUND' (line 52) rather than ApiErrorCode.NOT_FOUND. If NOT_FOUND is not part of the ApiErrorCode enum, this creates an inconsistency with the standardized error system.

Consider adding NOT_FOUND to the ApiErrorCode enum in packages/shared/src/types/error.ts or document why 404 responses use a different error format than other API errors. Alternatively, use Errors.unknown() pattern or create a dedicated Errors.notFound() factory method.

#!/bin/bash
# Description: Check if NOT_FOUND is defined in ApiErrorCode enum

ast-grep --pattern 'export enum ApiErrorCode {
  $$$
}'
packages/shared/src/types/error.ts (1)

72-92: Consider adding Error.captureStackTrace for cleaner stack traces.

While the current implementation correctly extends Error and calls super(), you could optionally add Error.captureStackTrace(this, ApiError) in the constructor to exclude the constructor frame from the stack trace in Node.js environments. This is a nice-to-have enhancement for debugging but not critical since modern JS already captures the stack properly.

  constructor(message: string, code: ApiErrorCode, details?: ApiErrorResponse['details']) {
    super(message)
    this.name = 'ApiError'
    this.code = code
    this.statusCode = ErrorCodeToStatus[code]
    this.details = details
+   
+   // Maintain proper stack trace for Node.js
+   if (Error.captureStackTrace) {
+     Error.captureStackTrace(this, ApiError)
+   }
  }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 20c285e and 6073e8c.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (58)
  • .github/ISSUE_TEMPLATE/bug_report.md (1 hunks)
  • .github/ISSUE_TEMPLATE/config.yml (1 hunks)
  • .github/ISSUE_TEMPLATE/feature_request.md (1 hunks)
  • .github/PULL_REQUEST_TEMPLATE.md (1 hunks)
  • .github/dependabot.yml (1 hunks)
  • .github/labeler.yml (1 hunks)
  • .github/workflows/ci.yml (1 hunks)
  • .github/workflows/deploy-cf-pages.yml (1 hunks)
  • .github/workflows/deploy-cf-workers.yml (1 hunks)
  • .github/workflows/docker.yml (1 hunks)
  • .github/workflows/labeler.yml (1 hunks)
  • .github/workflows/preview.yml (1 hunks)
  • .github/workflows/release.yml (1 hunks)
  • .github/workflows/security.yml (1 hunks)
  • .github/workflows/stale.yml (1 hunks)
  • .gitignore (0 hunks)
  • README.zh.md (1 hunks)
  • apps/api/package.json (1 hunks)
  • apps/api/src/__tests__/app.test.ts (1 hunks)
  • apps/api/src/__tests__/setup.ts (1 hunks)
  • apps/api/src/app.ts (11 hunks)
  • apps/api/src/middleware/__tests__/error-handler.test.ts (1 hunks)
  • apps/api/src/middleware/body-limit.ts (1 hunks)
  • apps/api/src/middleware/error-handler.ts (1 hunks)
  • apps/api/src/middleware/index.ts (1 hunks)
  • apps/api/src/middleware/logger.ts (1 hunks)
  • apps/api/src/middleware/request-id.ts (1 hunks)
  • apps/api/src/middleware/security.ts (1 hunks)
  • apps/api/src/middleware/timeout.ts (1 hunks)
  • apps/api/src/providers/__tests__/gitee.test.ts (1 hunks)
  • apps/api/src/providers/__tests__/huggingface.test.ts (1 hunks)
  • apps/api/src/providers/gitee.ts (3 hunks)
  • apps/api/src/providers/huggingface.ts (4 hunks)
  • apps/api/src/providers/modelscope.ts (3 hunks)
  • apps/api/src/utils/__tests__/format.test.ts (1 hunks)
  • apps/api/src/utils/format.ts (1 hunks)
  • apps/api/src/utils/gradio.ts (1 hunks)
  • apps/api/src/utils/index.ts (1 hunks)
  • apps/api/vitest.config.ts (1 hunks)
  • apps/web/package.json (1 hunks)
  • apps/web/src/__tests__/setup.ts (1 hunks)
  • apps/web/src/lib/__tests__/api.test.ts (1 hunks)
  • apps/web/src/lib/__tests__/constants.test.ts (1 hunks)
  • apps/web/src/lib/__tests__/utils.test.ts (1 hunks)
  • apps/web/src/lib/api.ts (4 hunks)
  • apps/web/vite.config.ts (1 hunks)
  • apps/web/vitest.config.ts (1 hunks)
  • docs/CHANGELOG.md (1 hunks)
  • docs/en/API.md (4 hunks)
  • docs/en/CONTRIBUTING.md (2 hunks)
  • docs/zh/API.md (4 hunks)
  • docs/zh/CONTRIBUTING.md (2 hunks)
  • package.json (1 hunks)
  • packages/shared/src/constants/models.ts (7 hunks)
  • packages/shared/src/types/error.ts (1 hunks)
  • packages/shared/src/types/index.ts (1 hunks)
  • packages/shared/src/utils/validation.test.ts (1 hunks)
  • packages/shared/src/utils/validation.ts (1 hunks)
💤 Files with no reviewable changes (1)
  • .gitignore
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{ts,tsx,js,jsx,json}

📄 CodeRabbit inference engine (CLAUDE.md)

Use Biome for all linting and formatting operations (pnpm lint, pnpm format, pnpm check)

Files:

  • apps/api/src/middleware/security.ts
  • apps/api/src/middleware/timeout.ts
  • apps/api/src/middleware/__tests__/error-handler.test.ts
  • apps/api/vitest.config.ts
  • apps/api/src/middleware/index.ts
  • apps/api/src/middleware/error-handler.ts
  • apps/api/src/middleware/request-id.ts
  • apps/api/src/middleware/body-limit.ts
  • apps/web/src/__tests__/setup.ts
  • apps/web/package.json
  • apps/api/src/utils/index.ts
  • apps/web/src/lib/__tests__/api.test.ts
  • apps/api/src/utils/format.ts
  • apps/web/vitest.config.ts
  • apps/web/src/lib/__tests__/constants.test.ts
  • apps/api/src/providers/__tests__/gitee.test.ts
  • packages/shared/src/types/error.ts
  • apps/api/src/__tests__/app.test.ts
  • apps/api/src/providers/__tests__/huggingface.test.ts
  • apps/api/src/utils/gradio.ts
  • apps/api/src/providers/huggingface.ts
  • packages/shared/src/utils/validation.ts
  • apps/web/src/lib/__tests__/utils.test.ts
  • apps/api/src/middleware/logger.ts
  • apps/api/src/utils/__tests__/format.test.ts
  • apps/api/package.json
  • apps/api/src/providers/modelscope.ts
  • apps/web/vite.config.ts
  • apps/api/src/providers/gitee.ts
  • packages/shared/src/constants/models.ts
  • packages/shared/src/types/index.ts
  • packages/shared/src/utils/validation.test.ts
  • apps/api/src/__tests__/setup.ts
  • package.json
  • apps/api/src/app.ts
  • apps/web/src/lib/api.ts
apps/api/src/**/*.ts

📄 CodeRabbit inference engine (CLAUDE.md)

apps/api/src/**/*.ts: POST /api/generate endpoint must support 'gitee' and 'huggingface' as provider parameters, require X-API-Key header for Gitee AI, and support optional X-HF-Token header for HuggingFace
Use Hono API framework for Cloudflare Workers implementation in the API app (apps/api)

Files:

  • apps/api/src/middleware/security.ts
  • apps/api/src/middleware/timeout.ts
  • apps/api/src/middleware/__tests__/error-handler.test.ts
  • apps/api/src/middleware/index.ts
  • apps/api/src/middleware/error-handler.ts
  • apps/api/src/middleware/request-id.ts
  • apps/api/src/middleware/body-limit.ts
  • apps/api/src/utils/index.ts
  • apps/api/src/utils/format.ts
  • apps/api/src/providers/__tests__/gitee.test.ts
  • apps/api/src/__tests__/app.test.ts
  • apps/api/src/providers/__tests__/huggingface.test.ts
  • apps/api/src/utils/gradio.ts
  • apps/api/src/providers/huggingface.ts
  • apps/api/src/middleware/logger.ts
  • apps/api/src/utils/__tests__/format.test.ts
  • apps/api/src/providers/modelscope.ts
  • apps/api/src/providers/gitee.ts
  • apps/api/src/__tests__/setup.ts
  • apps/api/src/app.ts
apps/web/src/**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

apps/web/src/**/*.{ts,tsx}: Use @/ path alias for imports in frontend code (maps to src/)
Persist settings and API keys to localStorage with AES-256-GCM encryption (use src/lib/crypto.ts)
Persist Flow mode state using IndexedDB storage (via src/lib/flow-storage.ts) for nodes, edges, and images
Configure API URL via VITE_API_URL environment variable, defaulting to relative path for same-origin deployment

Files:

  • apps/web/src/__tests__/setup.ts
  • apps/web/src/lib/__tests__/api.test.ts
  • apps/web/src/lib/__tests__/constants.test.ts
  • apps/web/src/lib/__tests__/utils.test.ts
  • apps/web/src/lib/api.ts
apps/web/src/**/*.{tsx,ts}

📄 CodeRabbit inference engine (CLAUDE.md)

Use React 19 with Vite, Tailwind CSS, and shadcn/ui components in the frontend (apps/web)

Files:

  • apps/web/src/__tests__/setup.ts
  • apps/web/src/lib/__tests__/api.test.ts
  • apps/web/src/lib/__tests__/constants.test.ts
  • apps/web/src/lib/__tests__/utils.test.ts
  • apps/web/src/lib/api.ts
🧠 Learnings (12)
📚 Learning: 2025-12-12T02:11:01.015Z
Learnt from: CR
Repo: WuMingDao/zenith-image-generator PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-12T02:11:01.015Z
Learning: Applies to apps/api/src/**/*.ts : Use Hono API framework for Cloudflare Workers implementation in the API app (apps/api)

Applied to files:

  • apps/api/src/middleware/security.ts
  • apps/api/src/middleware/error-handler.ts
  • apps/api/src/middleware/request-id.ts
  • apps/api/src/__tests__/app.test.ts
  • apps/api/package.json
  • apps/api/src/app.ts
📚 Learning: 2025-12-12T02:11:01.015Z
Learnt from: CR
Repo: WuMingDao/zenith-image-generator PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-12T02:11:01.015Z
Learning: Applies to apps/web/src/**/*.{ts,tsx} : Configure API URL via VITE_API_URL environment variable, defaulting to relative path for same-origin deployment

Applied to files:

  • apps/api/vitest.config.ts
  • apps/web/src/__tests__/setup.ts
  • apps/web/vitest.config.ts
  • apps/api/package.json
  • apps/web/vite.config.ts
  • apps/api/src/__tests__/setup.ts
  • apps/web/src/lib/api.ts
📚 Learning: 2025-12-12T02:11:01.015Z
Learnt from: CR
Repo: WuMingDao/zenith-image-generator PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-12T02:11:01.015Z
Learning: Run tests using Vitest with 'pnpm test' for watch mode or 'pnpm test:run' for single run, and use 'pnpm test:coverage' for coverage reports

Applied to files:

  • apps/api/vitest.config.ts
  • apps/web/src/__tests__/setup.ts
  • apps/web/vitest.config.ts
  • docs/zh/CONTRIBUTING.md
  • apps/api/package.json
  • docs/en/CONTRIBUTING.md
  • apps/api/src/__tests__/setup.ts
  • package.json
📚 Learning: 2025-12-12T02:11:01.015Z
Learnt from: CR
Repo: WuMingDao/zenith-image-generator PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-12T02:11:01.015Z
Learning: Applies to apps/api/src/**/*.ts : POST /api/generate endpoint must support 'gitee' and 'huggingface' as provider parameters, require X-API-Key header for Gitee AI, and support optional X-HF-Token header for HuggingFace

Applied to files:

  • README.zh.md
  • apps/web/src/lib/__tests__/api.test.ts
  • apps/api/src/providers/__tests__/gitee.test.ts
  • apps/api/src/__tests__/app.test.ts
  • apps/api/src/providers/__tests__/huggingface.test.ts
  • apps/api/src/providers/huggingface.ts
  • apps/api/src/providers/modelscope.ts
  • apps/api/src/providers/gitee.ts
  • docs/zh/API.md
  • apps/api/src/app.ts
📚 Learning: 2025-12-12T02:11:01.015Z
Learnt from: CR
Repo: WuMingDao/zenith-image-generator PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-12T02:11:01.015Z
Learning: Deploy API to Cloudflare Workers using 'wrangler deploy --minify src/index.ts' from apps/api directory

Applied to files:

  • .github/workflows/deploy-cf-pages.yml
  • .github/workflows/deploy-cf-workers.yml
  • apps/api/package.json
📚 Learning: 2025-12-12T02:11:01.015Z
Learnt from: CR
Repo: WuMingDao/zenith-image-generator PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-12T02:11:01.015Z
Learning: Applies to apps/web/src/**/*.{ts,tsx} : Persist settings and API keys to localStorage with AES-256-GCM encryption (use src/lib/crypto.ts)

Applied to files:

  • apps/web/src/__tests__/setup.ts
  • apps/web/src/lib/__tests__/constants.test.ts
📚 Learning: 2025-12-12T02:11:01.015Z
Learnt from: CR
Repo: WuMingDao/zenith-image-generator PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-12T02:11:01.015Z
Learning: Applies to apps/web/.env* : Set VITE_API_URL=http://localhost:8787 in apps/web/.env for local development when running frontend and API separately

Applied to files:

  • apps/web/src/__tests__/setup.ts
  • docs/zh/CONTRIBUTING.md
  • docs/en/CONTRIBUTING.md
  • apps/web/vite.config.ts
📚 Learning: 2025-12-12T02:11:01.015Z
Learnt from: CR
Repo: WuMingDao/zenith-image-generator PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-12T02:11:01.015Z
Learning: Applies to apps/web/src/**/*.{tsx,ts} : Use React 19 with Vite, Tailwind CSS, and shadcn/ui components in the frontend (apps/web)

Applied to files:

  • apps/web/src/__tests__/setup.ts
  • apps/web/package.json
  • apps/web/vitest.config.ts
  • apps/api/package.json
  • apps/web/vite.config.ts
  • package.json
📚 Learning: 2025-12-12T02:11:01.015Z
Learnt from: CR
Repo: WuMingDao/zenith-image-generator PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-12T02:11:01.015Z
Learning: Applies to apps/web/src/**/*.{ts,tsx} : Use @/ path alias for imports in frontend code (maps to src/)

Applied to files:

  • apps/web/vitest.config.ts
📚 Learning: 2025-12-12T02:11:01.015Z
Learnt from: CR
Repo: WuMingDao/zenith-image-generator PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-12T02:11:01.015Z
Learning: Implement provider abstraction pattern for API: define provider interface in apps/api/src/providers/types.ts, implement specific providers in individual files, and register/lookup providers in apps/api/src/providers/registry.ts

Applied to files:

  • apps/api/src/__tests__/app.test.ts
  • apps/api/src/providers/modelscope.ts
  • packages/shared/src/constants/models.ts
📚 Learning: 2025-12-12T02:11:01.015Z
Learnt from: CR
Repo: WuMingDao/zenith-image-generator PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-12T02:11:01.015Z
Learning: Run development with 'pnpm dev' for all apps or use app-specific commands: pnpm dev:web for frontend (port 5173) or pnpm dev:api for API (port 8787)

Applied to files:

  • docs/zh/CONTRIBUTING.md
  • docs/en/CONTRIBUTING.md
📚 Learning: 2025-12-12T02:11:01.015Z
Learnt from: CR
Repo: WuMingDao/zenith-image-generator PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-12T02:11:01.015Z
Learning: Applies to **/*.{ts,tsx,js,jsx,json} : Use Biome for all linting and formatting operations (pnpm lint, pnpm format, pnpm check)

Applied to files:

  • package.json
🧬 Code graph analysis (13)
apps/api/src/middleware/timeout.ts (1)
packages/shared/src/types/error.ts (1)
  • Errors (95-156)
apps/api/src/middleware/error-handler.ts (2)
packages/shared/src/types/error.ts (3)
  • ApiError (72-92)
  • Errors (95-156)
  • ApiErrorResponse (53-69)
apps/api/src/middleware/request-id.ts (1)
  • requestId (18-27)
apps/api/src/middleware/body-limit.ts (1)
apps/api/src/middleware/error-handler.ts (1)
  • sendError (28-32)
apps/web/src/lib/__tests__/api.test.ts (2)
apps/web/src/lib/api.ts (3)
  • getErrorMessage (45-69)
  • generateImage (91-143)
  • upscaleImage (148-188)
apps/api/src/index.ts (1)
  • fetch (17-21)
apps/web/src/lib/__tests__/constants.test.ts (1)
apps/web/src/lib/constants.ts (5)
  • loadSettings (49-56)
  • STORAGE_KEY (47-47)
  • saveSettings (58-60)
  • PROVIDER_OPTIONS (63-67)
  • getDefaultModel (70-73)
apps/api/src/providers/__tests__/huggingface.test.ts (2)
apps/api/src/providers/types.ts (1)
  • ProviderGenerateRequest (8-10)
apps/api/src/providers/huggingface.ts (1)
  • HuggingFaceProvider (147-178)
apps/api/src/providers/huggingface.ts (1)
packages/shared/src/types/error.ts (1)
  • Errors (95-156)
apps/web/src/lib/__tests__/utils.test.ts (1)
apps/web/src/lib/utils.ts (1)
  • cn (4-6)
apps/api/src/middleware/logger.ts (1)
apps/api/src/middleware/request-id.ts (1)
  • requestId (18-27)
apps/api/src/utils/__tests__/format.test.ts (1)
apps/api/src/utils/format.ts (2)
  • formatDimensions (16-21)
  • formatDuration (28-30)
apps/api/src/providers/modelscope.ts (1)
packages/shared/src/types/error.ts (1)
  • Errors (95-156)
apps/api/src/providers/gitee.ts (2)
packages/shared/src/types/error.ts (1)
  • Errors (95-156)
apps/api/src/providers/types.ts (3)
  • ImageProvider (21-28)
  • ProviderGenerateRequest (8-10)
  • ProviderGenerateResult (13-18)
apps/web/src/lib/api.ts (1)
packages/shared/src/types/error.ts (1)
  • ApiErrorResponse (53-69)
🪛 markdownlint-cli2 (0.18.1)
docs/en/CONTRIBUTING.md

161-161: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Workers Builds: zenith-image-generator-api
  • GitHub Check: Cloudflare Pages
🔇 Additional comments (84)
docs/CHANGELOG.md (1)

14-18: Changelog “Changed” items look consistent and user-relevant.

.github/workflows/docker.yml (1)

109-110: Verify VITE_API_URL configuration for the Web image.

The Web build passes VITE_API_URL= as an empty string. Confirm this is intentional and that the API endpoint is configured elsewhere (via environment variables, config file, or runtime injection). If the Web image requires a valid API URL to function properly, this should be set during the build or documented as a runtime requirement.

packages/shared/src/constants/models.ts (4)

5-5: LGTM!

The switch to single quotes is consistent with Biome formatting standards.


8-148: LGTM!

The formatting changes (single quotes, trailing semicolon removal) align with Biome standards. The addition of guidanceScale configurations for FLUX models enhances the model definitions with appropriate parameter constraints.


151-153: LGTM!

The arrow function formatting is consistent and follows Biome standards. The logic remains correct.

Also applies to: 161-166


95-103: The guidanceScale omission in the HuggingFace provider's flux-1-schnell model configuration is intentional and accurate. The HuggingFace Gradio API endpoint for this model has a fixed parameter structure [prompt, seed, false, width, height, steps] that does not include guidance scale, unlike the Gitee provider which supports it through its API. The configuration correctly reflects the actual capabilities of each provider.

packages/shared/src/types/index.ts (1)

9-11: LGTM!

The error type exports follow the established barrel export pattern and provide clean access to shared error utilities.

apps/api/src/utils/index.ts (1)

1-6: LGTM!

Clean utility barrel export that centralizes access to format and gradio utilities.

apps/api/package.json (2)

10-12: LGTM!

Test scripts follow the project conventions and align with the Vitest testing approach documented in learnings.

Based on learnings, these commands support watch mode (pnpm test), single run (pnpm test:run), and coverage reports (pnpm test:coverage).


22-27: TypeScript is correctly inherited from workspace root.

Verified: TypeScript ^5.7.2 is available at the root package.json and is accessible to apps/api via pnpm workspace hoisting (default behavior with no .npmrc). The CI pipeline successfully builds and type-checks the API package (pnpm build:api and pnpm -r exec tsc --noEmit), confirming the build works as intended.

docs/en/CONTRIBUTING.md (2)

55-84: Excellent testing documentation!

The new testing section provides clear guidance on running tests, test structure, and writing practices. This aligns well with the Vitest setup introduced in this PR and matches the project's testing conventions.

Based on learnings, these test commands are correct for Vitest usage.


126-199: Great additions for PR workflow and commit conventions!

The new sections provide clear guidance for contributors on:

  • Pre-submission checks to run locally
  • CI pipeline expectations
  • Commit message conventions with practical examples

This will help maintain code quality and consistency across contributions.

.github/workflows/stale.yml (1)

1-67: LGTM!

The stale bot configuration is well-structured with:

  • Appropriate timeouts (30 days for issues, 14 days for PRs)
  • Clear messaging for contributors
  • Sensible exempt labels (pinned, security, work in progress)
  • Auto-removal of stale label on updates

This will help maintain repository hygiene without being overly aggressive.

apps/web/src/lib/__tests__/utils.test.ts (1)

12-62: Excellent test coverage for cn() utility!

The tests comprehensively cover:

  • Basic merging and conditional classes
  • Falsy value filtering
  • Tailwind class deduplication via tailwind-merge
  • Object and array syntax
  • Edge cases (empty input, complex scenarios)

This provides strong validation of the className merging behavior.

apps/web/src/lib/__tests__/constants.test.ts (4)

91-121: LGTM!

Comprehensive validation of the ASPECT_RATIOS constant. The tests ensure all required ratios are present, have proper structure, and maintain expected properties (icons, presets, square dimensions for 1:1).


123-148: LGTM!

Provider options are correctly validated, including the new ModelScope provider. The requiresAuth flags align with the coding guidelines (HuggingFace doesn't require auth, Gitee and ModelScope do).


150-183: LGTM!

Good defensive testing with the type assertion on Line 169 to verify runtime fallback behavior. The tests ensure getDefaultModel handles all providers including unknown ones, and that STORAGE_KEY remains stable.


16-58: The test correctly focuses on non-sensitive settings storage. API keys and tokens are persisted separately via src/lib/crypto.ts using AES-256-GCM encryption (accessed through encryptAndStoreToken() and decryptTokenFromStore()), not through the loadSettings/saveSettings functions tested here. The settings object contains display preferences (prompt, dimensions, model selection) but not actual authentication credentials—those are managed independently with encryption.

docs/zh/CONTRIBUTING.md (2)

55-84: LGTM!

Clear and accurate testing documentation. The commands align with project conventions, and the guidance about mocking external APIs is helpful for contributors.


126-200: LGTM!

Excellent documentation of the PR workflow and commit conventions. The Conventional Commits format is properly explained with relevant examples that match the project structure.

apps/api/src/providers/modelscope.ts (3)

12-58: LGTM!

Well-structured error parsing that maps ModelScope API responses to standardized error types. The fallback chain for extracting error messages is robust, and the status/message-based categorization covers common API error scenarios.


99-115: LGTM!

Robust response handling with proper error parsing and graceful JSON parse failure handling. The check for missing image URL before returning is defensive and appropriate.


73-75: Remove arbitrary token length validation or implement format validation.

The 8-character minimum length check is inconsistent with ModelScope's actual token format (ms- prefix followed by a UUID, typically ~39 characters). This validation:

  • Does not align with other providers (Gitee and HuggingFace have no length checks)
  • Only partially validates the token, catching obviously short strings but missing format requirements
  • Should either be removed for consistency or expanded to validate the actual ms-UUID format
README.zh.md (1)

23-23: LGTM!

Correctly updates the feature list to include ModelScope alongside the existing Gitee AI and HuggingFace providers, consistent with the new provider implementation in this PR.

packages/shared/src/utils/validation.test.ts (1)

32-37: LGTM!

Tests correctly updated to validate the new 4,000-character limit. Consistent with the changes in validation.ts.

.github/ISSUE_TEMPLATE/config.yml (1)

1-8: LGTM!

Standard GitHub issue template configuration that appropriately directs users to documentation and discussions before opening issues. The contact links are correctly formatted.

apps/web/vite.config.ts (1)

8-12: The CORS restriction does not break the recommended development setup.

The frontend makes API requests using the VITE_API_URL environment variable configured in .env, which defaults to http://localhost:8787 for local development. These fetch calls go directly to the API server, not through the Vite dev server. The cors: false setting in Vite only restricts static asset serving from the dev server itself—it does not prevent fetch calls to external origins. CORS for API requests is properly handled on the API server side via the CORS_ORIGINS configuration in wrangler.toml, as documented in CONTRIBUTING.md (LAN Access section). The recommended setup is working as intended with the current configuration.

Likely an incorrect or invalid review comment.

apps/api/src/__tests__/setup.ts (1)

6-16: Mock cleanup looks good; keep console.error suppression scoped if enabled.

If you later uncomment the console.error spy, prefer scoping it to specific tests/suites to avoid masking real failures.

apps/api/src/utils/__tests__/format.test.ts (1)

9-61: Good coverage for current formatting behavior.

Optional: the 1000ms -> 1.0s assertion is duplicated across two tests; you can keep it or drop one for concision.

.github/ISSUE_TEMPLATE/feature_request.md (1)

1-35: Template looks clean and actionable.

.github/ISSUE_TEMPLATE/bug_report.md (1)

1-42: Solid bug-report template.

.github/PULL_REQUEST_TEMPLATE.md (1)

1-36: PR template is clear and consistent with the toolchain.

apps/web/package.json (1)

38-49: No action needed. All versions are compatible.

The dependencies in apps/web/package.json (Vite 7.2.4, @vitest/browser 4.0.15, jsdom 27.3.0) are mutually compatible. Both jsdom@27 and @vitest/browser@4 require Node >= 20.19.0, and your CI uses Node 20 (which defaults to the latest 20.x release, meeting the >= 20.19.0 requirement). Vitest 4 was released alongside Vite 7 support, and there are no known peer dependency conflicts. No changes required.

apps/web/src/__tests__/setup.ts (1)

1-52: LGTM! Solid test setup with proper mocking.

The test setup correctly:

  • Mocks localStorage with all required methods (getItem, setItem, removeItem, clear, length, key)
  • Provides proper null returns for missing keys
  • Stubs import.meta.env with environment variables
  • Clears mocks between tests to prevent test contamination
  • Restores mocks after each test

This aligns with the project's testing guidelines and provides a clean test environment for web package tests.

.github/workflows/deploy-cf-pages.yml (1)

1-49: LGTM! Clean Cloudflare Pages deployment workflow.

The workflow correctly:

  • Triggers on pushes to main and manual dispatch
  • Sets appropriate permissions (contents: read, deployments: write)
  • Uses frozen lockfile for reproducible builds
  • Builds dependencies in correct order (shared → web)
  • Deploys the web distribution to Cloudflare Pages
apps/api/src/middleware/index.ts (1)

1-10: LGTM (clean middleware barrel).
Nice consolidation point for app.ts wiring and imports.

apps/api/src/middleware/__tests__/error-handler.test.ts (3)

9-71: LGTM! Comprehensive test coverage for toApiError.

The test suite thoroughly covers ApiError passthrough, Error-to-ApiError conversion (including timeout detection), and unknown type handling. The assertions correctly validate code mapping, message preservation, and details propagation.


73-117: LGTM! ApiError class tests are well-structured.

Constructor and toResponse tests validate property initialization, status code mappings, and response structure with proper handling of optional details.


119-151: LGTM! Errors factory tests validate all key factory methods.

Tests correctly verify error code assignment, message composition, and details propagation for each factory method.

.github/dependabot.yml (3)

8-31: LGTM! Root package Dependabot configuration is well-structured.

The dev-dependencies grouping with minor/patch update types is appropriate for development tooling, and the PR limit of 10 provides flexibility for multiple dependency streams.


33-46: LGTM! API package configuration is appropriate.

The lower PR limit and scoped commit prefix align well with app-specific dependency management.


74-86: LGTM! GitHub Actions configuration is appropriate.

The configuration properly targets workflow files with suitable labeling and commit prefixes.

apps/api/src/providers/huggingface.ts (1)

66-80: LGTM! Error handling is now consistent with the centralized error system.

All error paths properly use the Errors factory or parseHuggingFaceError, ensuring structured error responses. The SSE response truncation to 200 characters (line 79) prevents excessive logging.

Also applies to: 96-102, 170-171

apps/api/src/utils/format.ts (3)

8-10: LGTM! Classic Euclidean algorithm implementation.

The recursive GCD implementation is correct and efficient for computing the greatest common divisor.


16-21: LGTM! Dimension formatting with aspect ratio is correctly implemented.

The function properly computes and displays the reduced aspect ratio using the GCD helper.


28-29: LGTM! Duration formatting is concise and correct.

The function appropriately formats durations with one decimal place for seconds and plain milliseconds for sub-second durations.

.github/workflows/release.yml (5)

30-46: LGTM! Setup steps are configured correctly.

Full history checkout, pnpm setup, and frozen lockfile installation are appropriate for a release workflow.


48-64: LGTM! Version bumping is correctly implemented.

The workflow properly configures git, reads the current version, and bumps it using npm version with --no-git-tag-version to defer tag creation.


66-96: Verify changelog categorization excludes duplicates.

The commit categorization (lines 78-84) filters commits by prefix, but the "Other" category (line 84) excludes all categorized prefixes. However, if a commit matches multiple categories (though unlikely with conventional commits), it might appear multiple times.

The current logic should work correctly for well-formed conventional commits, but consider whether commits can match multiple patterns. The grep -vE on line 84 properly excludes all known prefixes for "Other".


98-107: LGTM! Git commit and tag operations are properly sequenced.

The workflow correctly commits the version bump, creates an annotated tag, and pushes both to the repository.


109-126: LGTM! GitHub release creation is properly configured.

The release step correctly uses the GitHub API to create a release with changelog and comparison link. Note that all releases are created as non-draft, non-prerelease by default—workflow modifications would be needed for pre-releases.

apps/api/src/middleware/error-handler.ts (3)

11-23: LGTM! Error normalization logic is comprehensive.

The function correctly handles ApiError passthrough, timeout detection, and unknown error fallbacks with appropriate message preservation.


37-43: LGTM! Global error handler integrates well with request ID middleware.

The handler properly logs errors with request context and delegates to the standardized error response utility.


28-32: The status code type assertion on line 31 is correct and complete. The union 400 | 401 | 429 | 500 | 502 | 504 covers all possible status codes from ErrorCodeToStatus: 400 (validation errors), 401 (auth errors), 429 (rate limiting), 500 (generation failures and unknown errors), 502 (provider/upstream errors), and 504 (timeouts). No additional status codes exist in the mapping.

apps/web/src/lib/api.ts (5)

19-29: LGTM! Error types provide good client-side error handling structure.

The ApiErrorInfo interface properly aligns with the shared ApiErrorResponse type, and the discriminated ApiResponse union enables type-safe error handling.


32-42: LGTM! Error response parsing is defensive and correct.

The function safely handles unknown API error payloads with appropriate type guards and fallbacks.


45-69: LGTM! User-friendly error messages are well-crafted.

The function provides clear, actionable messages for each error code with appropriate context from error details (provider, retryAfter, upstream, etc.).


127-134: LGTM! generateImage error handling is now centralized and structured.

The updated error handling properly parses API errors, generates user-friendly messages, and returns structured error information.


172-179: LGTM! upscaleImage error handling follows the same improved pattern.

Consistent error handling with generateImage improves code maintainability and user experience.

apps/web/src/lib/__tests__/api.test.ts (3)

11-139: LGTM! Comprehensive test coverage for getErrorMessage.

Tests thoroughly validate all error code mappings, provider name handling, retry-after values, and fallback behavior. The mix of substring and exact assertions is appropriate.


141-256: LGTM! generateImage tests cover all key scenarios.

Tests validate success paths, error handling, network failures, and authentication header inclusion with proper mocking and assertions.


258-313: LGTM! upscaleImage tests validate core functionality.

Tests cover successful upscaling, HuggingFace token authentication, and error handling with appropriate assertions.

apps/api/src/providers/__tests__/gitee.test.ts (3)

1-55: Well-structured test setup and auth validation coverage.

The test file properly validates that AUTH_REQUIRED is thrown for both missing and empty tokens, which aligns with the coding guidelines requiring X-API-Key header for Gitee AI. The setup/teardown pattern with vi.stubGlobal and vi.unstubAllGlobals is clean.


57-174: Comprehensive successful request tests with good edge case coverage.

The tests thoroughly verify:

  • Correct API endpoint and HTTP method
  • Request body structure including all optional fields
  • Auth token whitespace trimming (Line 159-173) - this is a useful defensive check
  • Random seed generation bounds

176-278: Thorough error handling coverage.

The error mapping tests cover the expected scenarios:

  • 401 → AUTH_INVALID
  • 429 → RATE_LIMITED or QUOTA_EXCEEDED based on message content
  • 500 → PROVIDER_ERROR
  • JSON parse errors and malformed responses

The comment on Lines 216-217 documenting the expired token behavior is helpful for future maintainers.

apps/api/src/providers/__tests__/huggingface.test.ts (4)

11-17: Correct default request without auth token.

The defaultRequest intentionally omits authToken, which correctly reflects that HuggingFace provider doesn't require authentication per the coding guidelines (optional X-HF-Token header).


40-84: Clean helper functions for mocking Gradio API flow.

The helper functions (mockGradioSuccess, mockGradioQueueError, mockGradioSSEError) encapsulate the two-step Gradio queue mechanism (queue request → SSE result), making tests more readable and maintainable.


181-289: Comprehensive error handling tests.

Good coverage of various failure modes:

  • Queue request failures (500, 429, 401, 503)
  • SSE error events
  • Missing event_id
  • Empty/unexpected response formats
  • Timeout errors (504)

222-231: Test correctly uses 402 as a test fixture to isolate quota error handling.

The test does not claim HuggingFace returns HTTP 402. According to official HuggingFace documentation, quota/rate-limit errors return HTTP 429. The test deliberately uses 402 to verify that the error handler correctly identifies "quota exceeded" messages without triggering the 429 → RATE_LIMITED check that happens first in parseHuggingFaceError(). The existing comment adequately explains this rationale.

apps/api/src/__tests__/app.test.ts (5)

12-60: Well-defined response type interfaces.

The TypeScript interfaces for API responses provide type safety for test assertions and serve as documentation for the expected response shapes.


266-305: Authentication tests align with coding guidelines.

The tests correctly verify:

  • Gitee provider requires X-API-Key header (returns 401 without it)
  • HuggingFace provider works without authentication

This aligns with the coding guidelines: "require X-API-Key header for Gitee AI, and support optional X-HF-Token header for HuggingFace."

Based on learnings, the POST /api/generate endpoint correctly supports both 'gitee' and 'huggingface' as provider parameters.


368-391: Good test for default provider behavior.

The test verifies that when no provider is specified, the API defaults to 'gitee' and calls the Gitee API endpoint. This ensures backward compatibility.


422-457: Legacy endpoint backward compatibility tests.

Good coverage for the /api/generate-hf legacy endpoint, testing both successful generation and prompt validation. This ensures existing clients continue to work.


459-494: Upscale endpoint security tests.

The tests validate URL whitelisting (https://evil.com/image.png rejected) and scale validation. This is important for preventing SSRF attacks.

apps/api/src/utils/gradio.ts (2)

10-43: Robust SSE parsing with error handling.

The extractCompleteEventData function correctly:

  • Parses SSE format line by line
  • Handles both 'complete' and 'error' events
  • Falls back gracefully when JSON parsing fails (Line 30-32)
  • Provides debugging context for unexpected responses (Line 39-42)

62-69: This review comment targets dead code that is not actually used in the application.

The file apps/api/src/utils/gradio.ts is exported from the utils module but not imported anywhere in the codebase. The HuggingFace provider implements its own local callGradioApi function (lines 84-110 in apps/api/src/providers/huggingface.ts) with proper error code mapping via the parseHuggingFaceError function (lines 9-45). This function correctly maps HTTP status codes to specific error types:

  • 429 → RATE_LIMITED
  • 401/403 → AUTH_INVALID
  • 504 → TIMEOUT
  • 402 → QUOTA_EXCEEDED
  • 503 → PROVIDER_ERROR

The tests pass because they exercise the provider implementation, which already handles error code mapping correctly. No changes are needed.

Likely an incorrect or invalid review comment.

apps/api/src/app.ts (4)

44-77: Well-structured middleware stack with proper ordering.

The middleware is applied in the correct order:

  1. requestId - First for logging correlation
  2. corsMiddleware - Early for preflight handling
  3. securityHeaders - Security headers
  4. requestLogger - Logging with request ID available
  5. timeout and bodyLimit - Per-route limits for generation endpoints

The 120-second timeout and 50KB body limit are reasonable defaults for image generation.


123-209: POST /api/generate endpoint correctly implements provider requirements.

The endpoint:

  • Supports 'gitee' and 'huggingface' providers via providerId (Line 133)
  • Defaults to 'gitee' for backward compatibility (Line 133)
  • Extracts auth token from provider-specific header (Line 140): X-API-Key for Gitee, X-HF-Token for HuggingFace
  • Enforces auth requirement based on provider config (Lines 143-145)

This aligns with coding guidelines. Based on learnings, the implementation correctly requires X-API-Key for Gitee AI and supports optional X-HF-Token for HuggingFace.


211-282: Legacy endpoint maintains backward compatibility.

The /generate-hf endpoint:

  • Works without authentication (HuggingFace is optional)
  • Uses X-HF-Token header when provided
  • Defaults to 'z-image-turbo' model and standard dimensions

This ensures existing clients continue to work.


284-331: Upscale endpoint with URL whitelisting.

The endpoint correctly validates:

  • URL presence and type
  • URL against whitelist via isAllowedImageUrl (SSRF protection)
  • Scale parameter bounds

Error handling uses sendError consistently with the standardized error responses.

packages/shared/src/types/error.ts (4)

6-30: LGTM! Well-organized error code enum.

The enum is clearly structured with logical groupings and consistent naming conventions. The inline comments indicating HTTP status code categories are helpful for understanding the error hierarchy.


33-50: LGTM! Correct HTTP status code mappings.

The error-to-status mappings are semantically correct and type-safe. Using Record<ApiErrorCode, number> ensures all enum values are covered at compile time.


53-69: LGTM! Clear and flexible error response structure.

The interface provides a standardized format for API error responses with appropriate optional fields for context-specific details. The JSDoc comments enhance usability.


95-156: LGTM! Excellent error factory API.

The Errors helper object provides a clean, type-safe API for creating common errors with sensible defaults. The factory methods encapsulate error creation logic and ensure consistent error details across the application.

Comment on lines +40 to +46
- name: Deploy to Cloudflare Workers
uses: cloudflare/wrangler-action@v3
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}
workingDirectory: apps/api
command: deploy
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if wrangler.toml exists and examine its configuration

if [ -f "apps/api/wrangler.toml" ]; then
  echo "=== wrangler.toml configuration ==="
  cat apps/api/wrangler.toml
else
  echo "ERROR: apps/api/wrangler.toml not found!"
  echo ""
  echo "=== Checking apps/api directory structure ==="
  ls -la apps/api/ 2>/dev/null || echo "apps/api directory not found"
fi

Repository: WuMingDao/zenith-image-generator

Length of output: 868


Add minification to the Cloudflare Workers deployment.

The wrangler.toml correctly specifies the entry point (src/index.ts), so the simplified deploy command will work. However, minification is not configured. Add minification to the build configuration:

[build]
command = ""
minify = true

Alternatively, add the --minify flag to the workflow command if you prefer explicit control.

🤖 Prompt for AI Agents
.github/workflows/deploy-cf-workers.yml around lines 40 to 46: the workflow
deploy step does not enable minification for Cloudflare Workers; either add the
--minify flag to the wrangler command (e.g., set with.command to "deploy
--minify") in this workflow, or enable minification in the project's
wrangler.toml by adding a [build] section with minify = true (and an empty
command if needed); update one of these places so the worker bundle is minified
during deploy.

Comment on lines +14 to +28
return async (c, next) => {
const contentLength = c.req.header('Content-Length')

if (contentLength) {
const size = Number.parseInt(contentLength, 10)
if (size > maxSize) {
return sendError(
c,
Errors.invalidParams(
'body',
`Request body too large. Maximum size: ${Math.round(maxSize / 1024)}KB`
)
)
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n apps/api/src/middleware/body-limit.ts

Repository: WuMingDao/zenith-image-generator

Length of output: 1064


🏁 Script executed:

# Check how body-limit middleware is used
rg -n "bodyLimit" apps/api/src/ -A 2 -B 2

Repository: WuMingDao/zenith-image-generator

Length of output: 1077


🏁 Script executed:

# Check the POST /api/generate endpoint implementation
fd "generate" apps/api/src/ --type f

Repository: WuMingDao/zenith-image-generator

Length of output: 58


🏁 Script executed:

# Look at how Hono handles request bodies
rg -n "req\.raw|req\.text|req\.json|body" apps/api/src/ -t ts | head -40

Repository: WuMingDao/zenith-image-generator

Length of output: 2865


🏁 Script executed:

# Check package.json for Hono version
cat apps/api/package.json | grep -A 2 -B 2 "hono"

Repository: WuMingDao/zenith-image-generator

Length of output: 223


🏁 Script executed:

# Check if there's any Hono configuration for body size
rg -n "maxBodySize|maxSize|limit" apps/api/src/app.ts -B 3 -A 3

Repository: WuMingDao/zenith-image-generator

Length of output: 364


🏁 Script executed:

# Check if there's any Cloudflare Workers middleware handling size
rg -n "Cloudflare|wrangler|miniflare" apps/api/ -r

Repository: WuMingDao/zenith-image-generator

Length of output: 489


🏁 Script executed:

# Look at the error handler to understand error handling patterns
cat -n apps/api/src/middleware/error-handler.ts

Repository: WuMingDao/zenith-image-generator

Length of output: 1832


🌐 Web query:

Hono 4.6.0 body size limit default maximum request body size enforcement

💡 Result:

  • Default maximum request body size in Hono's bodyLimit middleware: 100 * 1024 bytes (100 KB). [1]

  • How it's enforced in v4.6.0: the middleware checks Content-Length first (if present) and otherwise reads the request stream and triggers the configured onError when the size exceeds maxSize. [1]

  • Important security note: versions <= 4.9.6 (which includes 4.6.0) had a bug that could allow bypassing the limit when conflicting headers (Transfer-Encoding: chunked vs Content-Length) were present; this was fixed in 4.9.7. If you rely on body size limits, upgrade or apply mitigations. [2]

Sources:
[1] Hono bodyLimit middleware docs — default maxSize and behavior.
[2] GHSA / CVE advisory for bodyLimit bypass (fixed in 4.9.7).


🌐 Web query:

Hono framework Content-Length header validation req.json() maximum body size

💡 Result:

Short answer: Hono's bodyLimit middleware uses the Content-Length header (if present) to check request size; if Content-Length is missing it reads the stream and enforces the maxSize while streaming. This behavior meant a past bug where Content-Length was (incorrectly) prioritized over Transfer-Encoding: chunked — fixed in Hono v4.9.7. Use bodyLimit({ maxSize }) and upgrade to >= v4.9.7; also ensure your runtime/proxy request-body limits (e.g., Bun, Cloudflare, etc.) are configured appropriately. [1][2][3]

Sources:

  • bodyLimit docs (behavior + runtime note). [1]
  • Hono security advisory / GHSA describing the Content-Length vs Transfer-Encoding fix (fixed in v4.9.7). [2]
  • Hono docs overview. [3]

🏁 Script executed:

# Check git history or comments for why custom bodyLimit was created
rg -n "body.*limit|Content-Length" apps/api/src/ -B 2 -A 2

Repository: WuMingDao/zenith-image-generator

Length of output: 1320


🏁 Script executed:

# Check if body is being read/streamed anywhere before size check
rg -n "req\.raw|getRawBody|readable\|stream" apps/api/src/middleware/ -A 3

Repository: WuMingDao/zenith-image-generator

Length of output: 58


🏁 Script executed:

# Look at how Hono's built-in bodyLimit is exposed
rg -n "from 'hono" apps/api/src/middleware/body-limit.ts

Repository: WuMingDao/zenith-image-generator

Length of output: 123


🌐 Web query:

Cloudflare Workers maximum request body size limit default

💡 Result:

Cloudflare enforces a network-wide request-body size limit tied to your Cloudflare account plan (not the Workers plan): Free/Pro = 100 MB, Business = 200 MB, Enterprise = 500 MB (Enterprise can request higher). [1]

Note: some Cloudflare features (e.g., Rules Engine body fields) have much smaller parsing limits (e.g., 128 KB for Enterprise, 1 MB for Free) — those are separate from the network request-body limit. [2]

Sources: Cloudflare Workers limits; Ruleset-engine http.request.body docs. [1][2]


🌐 Web query:

Hono bodyLimit security bug CVE Content-Length Transfer-Encoding chunked v4.6.0 v4.9.7

💡 Result:

Short answer: This is CVE-2025-59139 — a request-body size limit bypass in Hono's bodyLimit middleware where Content-Length was honored even when Transfer-Encoding: chunked was present. Affects hono versions <= 4.9.6 (so v4.6.0 is vulnerable); fixed in v4.9.7. Upgrade to >=4.9.7. [1][2][3]

References:

  • GitHub security advisory (vendor) — details + patched version. [1]
  • NVD entry for CVE-2025-59139. [2]
  • Patch/commit that fixes the behavior. [3]

Links (by reference):
[1] GitHub advisory: honojs/hono GHSA-92vj-g62v-jqhh.
[2] NVD: CVE-2025-59139.
[3] Fix commit: 605c70560b52f13af10379f79b76717042fafe8d.


🏁 Script executed:

# Check if Hono's bodyLimit is imported/available
rg -n "hono/middleware" apps/api/src/

Repository: WuMingDao/zenith-image-generator

Length of output: 58


🏁 Script executed:

# Look at middleware/index to see what's exported
cat -n apps/api/src/middleware/index.ts

Repository: WuMingDao/zenith-image-generator

Length of output: 344


🏁 Script executed:

# Check if the streaming body is accessed anywhere in request handling
rg -n "c\.req\.raw\|req\.body\|readable" apps/api/src/ -t ts

Repository: WuMingDao/zenith-image-generator

Length of output: 58


Implement streaming body size enforcement and upgrade Hono due to security vulnerabilities.

The current implementation has two critical issues:

  1. Content-Length bypass: The middleware only checks the Content-Length header but doesn't enforce size limits on the actual request body during streaming. A client can omit this header to bypass the limit entirely.

  2. CVE-2025-59139 vulnerability: Hono 4.6.0 is vulnerable to a request-body size limit bypass where Content-Length is honored even when Transfer-Encoding: chunked is present. This allows attackers to send arbitrarily large payloads.

Recommended solutions (in order of preference):

  1. Upgrade Hono to v4.9.7 or later (patches CVE-2025-59139) and switch to Hono's built-in bodyLimit middleware, which properly enforces limits during streaming.

  2. If staying with the custom middleware, implement streaming body size enforcement by reading chunks and tracking cumulative size, then reject when exceeding maxSize.

  3. Avoid requiring Content-Length as a mandatory header—it breaks valid Transfer-Encoding: chunked requests and is not a valid security measure on its own.

🤖 Prompt for AI Agents
In apps/api/src/middleware/body-limit.ts around lines 14-28, the middleware only
checks the Content-Length header (which can be omitted or spoofed) and is
vulnerable to the Hono CVE-2025-59139 request-body limit bypass; fix by
upgrading Hono to v4.9.7+ and replacing this custom logic with Hono's built-in
bodyLimit middleware so limits are enforced while streaming, or if you cannot
upgrade implement streaming enforcement: read the request body as a stream,
accumulate chunk sizes, immediately reject and abort the request when cumulative
size > maxSize, and do not rely on the Content-Length header (support chunked
transfer encoding).

Comment on lines +10 to +27
function generateRequestId(): string {
return `${Date.now().toString(36)}-${Math.random().toString(36).substring(2, 9)}`
}

/**
* Request ID middleware
* Assigns a unique ID to each request for logging and tracing
*/
export const requestId: MiddlewareHandler = async (c, next) => {
// Use existing request ID from header if provided, otherwise generate new one
const id = c.req.header('X-Request-ID') || generateRequestId()
c.set('requestId', id)

await next()

// Add request ID to response header
c.res.headers.set('X-Request-ID', id)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Validate incoming X-Request-ID before reflecting it in the response.
As written, any client-provided value is echoed back; add a length/charset guard (and generate a new ID if invalid). Also consider crypto.randomUUID() when available for higher-quality IDs.

 function generateRequestId(): string {
-  return `${Date.now().toString(36)}-${Math.random().toString(36).substring(2, 9)}`
+  if (typeof crypto !== 'undefined' && 'randomUUID' in crypto) {
+    return crypto.randomUUID()
+  }
+  return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`
 }
 
 export const requestId: MiddlewareHandler = async (c, next) => {
   // Use existing request ID from header if provided, otherwise generate new one
-  const id = c.req.header('X-Request-ID') || generateRequestId()
+  const candidate = c.req.header('X-Request-ID')?.trim()
+  const id =
+    candidate && /^[A-Za-z0-9._-]{1,128}$/.test(candidate) ? candidate : generateRequestId()
   c.set('requestId', id)
 
   await next()
 
   // Add request ID to response header
   c.res.headers.set('X-Request-ID', id)
 }

Comment on lines +11 to +25
export const securityHeaders: MiddlewareHandler = async (c, next) => {
await next()

// Prevent MIME type sniffing
c.res.headers.set('X-Content-Type-Options', 'nosniff')

// Prevent clickjacking
c.res.headers.set('X-Frame-Options', 'DENY')

// XSS protection (legacy browsers)
c.res.headers.set('X-XSS-Protection', '1; mode=block')

// Referrer policy
c.res.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin')
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Add modern security headers for comprehensive protection.

Consider adding these essential security headers:

  1. Content-Security-Policy (CSP) - Prevents XSS attacks by controlling resource loading
  2. Strict-Transport-Security (HSTS) - Forces HTTPS connections
  3. Permissions-Policy - Controls browser feature access

Also consider upgrading X-Frame-Options: DENY to the modern Content-Security-Policy: frame-ancestors 'none' directive.

Apply this diff to add modern security headers:

   await next()
 
   // Prevent MIME type sniffing
   c.res.headers.set('X-Content-Type-Options', 'nosniff')
 
   // Prevent clickjacking
   c.res.headers.set('X-Frame-Options', 'DENY')
 
-  // XSS protection (legacy browsers)
-  c.res.headers.set('X-XSS-Protection', '1; mode=block')
-
   // Referrer policy
   c.res.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin')
+
+  // Content Security Policy
+  c.res.headers.set(
+    'Content-Security-Policy',
+    "default-src 'self'; frame-ancestors 'none'; base-uri 'self'; form-action 'self'"
+  )
+
+  // HSTS (only if serving over HTTPS)
+  c.res.headers.set('Strict-Transport-Security', 'max-age=31536000; includeSubDomains')
+
+  // Permissions Policy
+  c.res.headers.set('Permissions-Policy', 'geolocation=(), microphone=(), camera=()')
 }

Note: Adjust CSP directives based on your actual resource loading requirements, and only enable HSTS if your API is served over HTTPS.

🤖 Prompt for AI Agents
In apps/api/src/middleware/security.ts around lines 11 to 25, the middleware
only sets legacy headers and is missing modern security headers; add a
Content-Security-Policy header (including a frame-ancestors 'none' directive to
replace X-Frame-Options), add Strict-Transport-Security (HSTS) header but only
enable/populate it when the app is served over HTTPS or behind a TLS terminator,
and add a Permissions-Policy header with a conservative feature list (e.g.,
disable geolocation, camera, microphone) while keeping existing safe headers (or
remove X-Frame-Options if CSP frame-ancestors is used); ensure CSP directives
are minimal and reviewed for your resource needs and that HSTS includes max-age
and includeSubDomains/preload only when appropriate.

Comment on lines +20 to +21
// XSS protection (legacy browsers)
c.res.headers.set('X-XSS-Protection', '1; mode=block')
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Remove deprecated X-XSS-Protection header.

The X-XSS-Protection header is deprecated and can introduce vulnerabilities in some browsers. Modern browsers use Content Security Policy (CSP) for XSS protection instead. This header should be removed.

Apply this diff:

-  // XSS protection (legacy browsers)
-  c.res.headers.set('X-XSS-Protection', '1; mode=block')
-
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// XSS protection (legacy browsers)
c.res.headers.set('X-XSS-Protection', '1; mode=block')
🤖 Prompt for AI Agents
In apps/api/src/middleware/security.ts around lines 20-21, remove the deprecated
X-XSS-Protection header assignment (c.res.headers.set('X-XSS-Protection', '1;
mode=block')) from the middleware; delete that line and any related comment, and
ensure you rely on Content Security Policy (CSP) elsewhere (or add/update CSP
headers if not present) for XSS protections.

Comment on lines +64 to +80
describe('isHuggingFaceUrl (tested via behavior)', () => {
// These tests document the expected behavior of URL detection
// The actual function is private, but we test the concept

const testUrls = [
{ url: 'https://example.hf.space/file/image.png', expected: true },
{ url: 'https://huggingface.co/datasets/image.png', expected: true },
{ url: 'https://mrfakename-z-image-turbo.hf.space/img.png', expected: true },
{ url: 'https://example.com/image.png', expected: false },
{ url: 'https://gitee.ai/image.png', expected: false },
]

it.each(testUrls)('should correctly identify $url as HF=$expected', ({ url, expected }) => {
const isHF = url.includes('.hf.space') || url.includes('huggingface.co')
expect(isHF).toBe(expected)
})
})
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Test doesn't validate the actual isHuggingFaceUrl implementation.

The test on lines 76-79 creates a local variable isHF with inline logic rather than testing the actual isHuggingFaceUrl function. This provides false confidence—if the real function's logic changes or has bugs, this test won't catch it.

Recommended solutions:

  1. Export the function for testing (add to exports in utils.ts)
  2. Test indirectly through public APIs that use isHuggingFaceUrl
  3. Remove this test if the function truly cannot be tested

The current approach documents intended behavior but doesn't validate actual implementation.

Example fix (Option 1 - export for testing):

In apps/web/src/lib/utils.ts:

// Export for testing
export function isHuggingFaceUrl(url: string): boolean {
  return url.includes('.hf.space') || url.includes('huggingface.co')
}

Then update the test:

-describe('isHuggingFaceUrl (tested via behavior)', () => {
-  // These tests document the expected behavior of URL detection
-  // The actual function is private, but we test the concept
+import { isHuggingFaceUrl } from '../utils'
 
+describe('isHuggingFaceUrl', () => {
   const testUrls = [
     { url: 'https://example.hf.space/file/image.png', expected: true },
     { url: 'https://huggingface.co/datasets/image.png', expected: true },
     { url: 'https://mrfakename-z-image-turbo.hf.space/img.png', expected: true },
     { url: 'https://example.com/image.png', expected: false },
     { url: 'https://gitee.ai/image.png', expected: false },
   ]
 
   it.each(testUrls)('should correctly identify $url as HF=$expected', ({ url, expected }) => {
-    const isHF = url.includes('.hf.space') || url.includes('huggingface.co')
-    expect(isHF).toBe(expected)
+    expect(isHuggingFaceUrl(url)).toBe(expected)
   })
 })
🤖 Prompt for AI Agents
In apps/web/src/lib/__tests__/utils.test.ts around lines 64 to 80, the test
defines a local isHF variable instead of invoking the real isHuggingFaceUrl
function; export the isHuggingFaceUrl function from apps/web/src/lib/utils.ts
(add an exported function that implements the current logic), update the test to
import that function from the module, and replace the inline url.includes(...)
logic with a call to isHuggingFaceUrl(url) so the test validates the actual
implementation (alternatively, if you cannot export it, remove the test or
assert via a public API that uses the function).

Comment thread docs/en/CONTRIBUTING.md
Comment on lines +161 to +166
```
<type>(<scope>): <description>

[optional body]
```

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Add language identifier to fenced code block.

The fenced code block is missing a language specifier, which triggers a markdown linting warning.

Apply this diff:

-```
+```text
 <type>(<scope>): <description>
 
 [optional body]
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

161-161: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

🤖 Prompt for AI Agents
In docs/en/CONTRIBUTING.md around lines 161 to 166, the fenced code block lacks
a language specifier which causes markdown lint warnings; update the opening
fence to include the language identifier by changing the triple backticks to use
"text" (i.e., replace ``` with ```text) so the block reads with a language tag
while leaving the inner content unchanged.

Comment thread package.json Outdated
Comment on lines +35 to +38
"@vitest/coverage-v8": "^4.0.15",
"turbo": "^2.3.3",
"typescript": "^5.7.2",
"vitest": "^2.1.8"
"vitest": "^4.0.15"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

cd . && pnpm test:run

Repository: WuMingDao/zenith-image-generator

Length of output: 291


🌐 Web query:

Vitest v4 breaking changes migration guide from v2

💡 Result:

Here are the key breaking changes and migration notes when moving from Vitest v2 → v4 (concise checklist — read the linked Migration Guide & release post for details):

  • Mocking and spies

    • vi.fn().getMockName() default name changed (may affect snapshots). Spies from vi.spyOn keep original name. (main.vitest.dev)
    • vi.restoreAllMocks no longer resets automocks (only restores spies created with vi.spyOn). restoreMocks config behavior changed. (main.vitest.dev)
    • vi.spyOn on an already-mocked function returns the same mock; automocked instance methods isolation changed. (main.vitest.dev)
  • Module/runtime changes

    • Vitest no longer injects __vitest_executor; it injects moduleRunner (ModuleRunner instance) instead. (main.vitest.dev)
    • vitest/execute entry point removed (was internal). (main.vitest.dev)
    • vite-node is no longer a dependency of Vitest. (main.vitest.dev)
  • Configuration / env / vars

    • Environment variable VITE_NODE_DEPS_MODULE_DIRECTORIES renamed to VITEST_MODULE_DIRECTORIES. (main.vitest.dev)
    • deps.optimizer.web renamed to deps.optimizer.client (or use custom names for other server environments). (main.vitest.dev)
    • Custom environments: transformMode → provide viteEnvironment (or rely on environment name + server.environments). (main.vitest.dev)
  • Browser mode & providers

    • Browser Mode graduated to stable; browser providers are now separate packages (e.g. @vitest/browser-playwright, @vitest/browser-webdriverio, @vitest/browser-preview) — you must install/configure provider packages if using browser mode. Import paths for context moved to vitest/browser (old path kept until next major). (vitest.dev)
  • New/changed test features & reporters

    • Visual regression built-in (toMatchScreenshot) and Playwright trace support added (new options like browser.trace). (vitest.dev)
    • Some reporter defaults removed/changed (basic reporter removed; default reporter prints tree only for single-file runs; new tree reporter available) — update custom reporter/config if you relied on old behaviour. (infoq.com)
  • Other notes

    • Several new public APIs and helpers (experimental_parseSpecifications, watcher, enableCoverage/disableCoverage, getSeed, etc.) — check whether you can replace any internal usage with the new public API. (vitest.dev)

Recommended migration steps

  1. Read the official Migration Guide and changelog and search your codebase for usages of: vi.restoreAllMocks, vi.fn().getMockName(), vitest/execute, VITE_NODE_DEPS_MODULE_DIRECTORIES, vite-node, deps.optimizer.web, custom environment transformMode, and any custom reporters. (main.vitest.dev)
  2. Install needed browser provider packages if you use Browser Mode. (vitest.dev)
  3. Run tests, update affected snapshots (mock name changes), and fix any mock/spy-related assumptions. (main.vitest.dev)

Primary sources

  • Vitest 4 announcement & migration links. (vitest.dev)
  • Official Migration Guide (breaking changes details). (main.vitest.dev)
  • Coverage of notable changes (browser mode, reporters, traces). (infoq.com)

If you want, I can produce a grep/replace list of code changes to run across your repo (examples for package installs, config key renames, and common mock/spy fixes).

Citations:


🏁 Script executed:

# Check if vitest is actually in node_modules (it's missing)
ls -la node_modules | grep vitest || echo "vitest not found in node_modules"

# Check the actual versions currently specified vs. what's in node_modules
cat package.json | grep -A 2 -B 2 "vitest"

# Search for breaking change patterns in test files
rg "(vi\.restoreAllMocks|vitest/execute|transformMode|VITE_NODE_DEPS|deps\.optimizer\.web)" --type ts --type tsx --type js --type jsx

Repository: WuMingDao/zenith-image-generator

Length of output: 666


🏁 Script executed:

# Check for usage of vi.fn().getMockName() which changed behavior in v4
rg "getMockName|spyOn.*already.*mocked|__vitest_executor|moduleRunner" --type ts --type tsx --type js --type jsx

Repository: WuMingDao/zenith-image-generator

Length of output: 103


🏁 Script executed:

# Find all test files and config files
fd "vitest|test\.(ts|js)" --type f | head -20

# Find vitest config file
fd "vitest\.config\.(ts|js)" --type f

# Search for breaking patterns with simpler approach (no -t flag)
rg "restoreAllMocks|vitest/execute|transformMode|VITE_NODE_DEPS|optimizer\.web" .

Repository: WuMingDao/zenith-image-generator

Length of output: 732


🏁 Script executed:

# Check for Vitest configuration details
fd "vitest\.config" . --type f -x cat {}

# Check if there are test files and what they look like
fd "\.(test|spec)\.(ts|js)" . --type f -x head -20 {} \; | head -50

Repository: WuMingDao/zenith-image-generator

Length of output: 2673


Test setup requires changes for Vitest v4 compatibility.

The upgrade to Vitest v4.0.15 introduces breaking changes that affect your test infrastructure. Specifically, vi.restoreAllMocks() is called in setup files (apps/web/src/__tests__/setup.ts and apps/api/src/__tests__/setup.ts), but v4 changed its behavior—it no longer resets automocks, only restores spies created with vi.spyOn(). Review the Vitest v4 migration guide and update setup files to use vi.clearAllMocks() or adjust mock handling accordingly. Also audit test snapshots for changes in vi.fn().getMockName() default naming behavior in v4.

🤖 Prompt for AI Agents
In package.json around lines 35 to 38, the project upgraded Vitest to v4 which
changed mock restoration semantics so existing setup files calling
vi.restoreAllMocks() no longer clear automocks; update your test setup files
(apps/web/src/__tests__/setup.ts and apps/api/src/__tests__/setup.ts) to call
vi.clearAllMocks() (or both vi.clearAllMocks() and vi.restoreAllMocks() where
you need to both clear mock state and restore spies) and adjust any tests
relying on vi.fn().getMockName() default names by updating snapshots or
explicitly naming mocks with vi.fn().mockName('name') to keep snapshot
stability.

Comment on lines 156 to 158
export function getModelConfig(modelId: string): ModelConfig | undefined {
return MODEL_CONFIGS.find((m) => m.id === modelId);
return MODEL_CONFIGS.find((m) => m.id === modelId)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find all usages of getModelConfig function
rg -n 'getModelConfig\(' --type ts --type tsx -A 3

# Also check imports to understand exposure
rg -n 'export.*getModelConfig' --type ts --type tsx

Repository: WuMingDao/zenith-image-generator

Length of output: 131


🏁 Script executed:

# Examine the models.ts file to understand the structure and verify the claims
head -200 packages/shared/src/constants/models.ts | tail -100

Repository: WuMingDao/zenith-image-generator

Length of output: 2490


🏁 Script executed:

# Check if there are actual duplicate model IDs across providers
rg -n "id:\s*['\"]z-image-turbo['\"]|id:\s*['\"]flux-1-schnell['\"]" packages/shared/src/constants/models.ts -B 5

Repository: WuMingDao/zenith-image-generator

Length of output: 578


🏁 Script executed:

# Search for usages with corrected flags
rg 'getModelConfig\(' --type ts -A 3 -B 1

# Also search without type filter to catch all files
rg 'getModelConfig\(' -A 3 -B 1

Repository: WuMingDao/zenith-image-generator

Length of output: 811


🏁 Script executed:

# Check for imports and re-exports
rg 'getModelConfig' --type ts

Repository: WuMingDao/zenith-image-generator

Length of output: 190


🏁 Script executed:

# Search for any imports of getModelConfig from models.ts
rg 'from.*models' --type ts -A 2 -B 1 | grep -A 2 -B 1 'getModelConfig'

# Broader search for any reference to getModelConfig
rg 'getModelConfig' -r . --no-ignore

Repository: WuMingDao/zenith-image-generator

Length of output: 177


🏁 Script executed:

# Check if models.ts is exported from an index file
rg 'export.*from.*models' --type ts

# Check the entire shared package structure
find packages/shared -name 'index.ts' -o -name 'index.js' | head -5

Repository: WuMingDao/zenith-image-generator

Length of output: 277


🏁 Script executed:

# Check the shared package exports
cat packages/shared/src/index.ts 2>/dev/null || cat packages/shared/index.ts 2>/dev/null || echo "No index found"

Repository: WuMingDao/zenith-image-generator

Length of output: 272


🏁 Script executed:

# Final confirmation: check MODEL_CONFIGS array order to confirm Gitee comes first
sed -n '8,100p' packages/shared/src/constants/models.ts | grep -E "provider:|id:" | head -20

Repository: WuMingDao/zenith-image-generator

Length of output: 528


Ambiguous behavior with duplicate model IDs across providers.

The getModelConfig(modelId) function returns the first match from MODEL_CONFIGS, which always returns the Gitee provider version when duplicate IDs exist (e.g., z-image-turbo, flux-1-schnell appear in both Gitee and HuggingFace). Since this function is exported as part of the public API (@z-image/shared), consumers may unknowingly receive the wrong provider's configuration. Use getModelByProviderAndId(provider, modelId) when the provider is known, or add clear documentation about this behavior.

Comment on lines +19 to +20
if (prompt.length > 4000) {
return { valid: false, error: 'Prompt exceeds maximum length of 4000 characters' }
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's examine the validation.ts file to see the current code and any comments
cat -n packages/shared/src/utils/validation.ts | head -50

Repository: WuMingDao/zenith-image-generator

Length of output: 1942


🏁 Script executed:

# Search for any documentation about the prompt length limit change
rg -n -C5 '4000|10000|prompt.*length|length.*limit' packages/shared/src/

Repository: WuMingDao/zenith-image-generator

Length of output: 2442


🏁 Script executed:

# Check provider implementations for prompt length limits
rg -n -C3 'prompt.*length|max.*prompt|provider' packages/api/src/ 2>/dev/null || rg -n -C3 'prompt.*length|max.*prompt' . --type ts --type tsx 2>/dev/null | head -100

Repository: WuMingDao/zenith-image-generator

Length of output: 58


🏁 Script executed:

# Look for CHANGELOG or release notes
find . -name "CHANGELOG*" -o -name "RELEASE*" -o -name "HISTORY*" | head -10

Repository: WuMingDao/zenith-image-generator

Length of output: 95


🏁 Script executed:

# Check git history for the prompt limit change (if available)
git log --oneline --all -S "4000" -- packages/shared/src/utils/validation.ts 2>/dev/null | head -20

Repository: WuMingDao/zenith-image-generator

Length of output: 130


🏁 Script executed:

# Check CHANGELOG for entries about prompt length limit
cat -n docs/CHANGELOG.md | grep -i -C5 'prompt\|4000\|10000\|limit'

Repository: WuMingDao/zenith-image-generator

Length of output: 976


🏁 Script executed:

# Look for all references to prompt limit in the entire codebase
rg -i 'prompt.*limit|max.*prompt|prompt.*4000|prompt.*10000' --type ts --type tsx --type json

Repository: WuMingDao/zenith-image-generator

Length of output: 103


🏁 Script executed:

# Check git history more thoroughly for any previous limit values
git log --all --oneline -S "prompt" -- packages/shared/src/utils/validation.ts 2>/dev/null | head -30

Repository: WuMingDao/zenith-image-generator

Length of output: 180


🏁 Script executed:

# Search for provider implementations or configuration
find . -path "*/node_modules" -prune -o -type f \( -name "*provider*" -o -name "*config*" \) -print | grep -E "\.(ts|tsx|js|json)$" | head -20

Repository: WuMingDao/zenith-image-generator

Length of output: 456


🏁 Script executed:

# Look for any documentation about API constraints
rg -i 'api.*limit|constraint|gitee|huggingface|modelscope' --type ts --type tsx --type json docs/ 2>/dev/null | head -30

Repository: WuMingDao/zenith-image-generator

Length of output: 58


🏁 Script executed:

# Check config file for prompt limit settings
cat -n apps/api/src/config.ts

Repository: WuMingDao/zenith-image-generator

Length of output: 1883


🏁 Script executed:

# Check provider constants
cat -n packages/shared/src/constants/providers.ts

Repository: WuMingDao/zenith-image-generator

Length of output: 1623


🏁 Script executed:

# Search for "10000" anywhere in the codebase
rg -n '10000' --type ts --type js --type json

Repository: WuMingDao/zenith-image-generator

Length of output: 356


🏁 Script executed:

# Check for any provider-specific implementations with prompt limits
rg -n -B5 -A5 'prompt' apps/api/src/ --type ts | head -100

Repository: WuMingDao/zenith-image-generator

Length of output: 6276


🏁 Script executed:

# Check the complete git log for the validation file to see when it was created
git log --all --oneline -- packages/shared/src/utils/validation.ts 2>/dev/null

Repository: WuMingDao/zenith-image-generator

Length of output: 180


Add inline documentation explaining the 4000-character prompt limit.

The current code enforces a 4000-character limit on prompts but provides no rationale. Please clarify:

  1. Is this limit driven by a provider API constraint (Gitee AI, HuggingFace, or ModelScope)?
  2. Is this a performance or cost optimization measure?
  3. Should this be documented in a code comment or the CHANGELOG?

The validation is enforced at lines 19–20, but there is no documentation explaining this specific threshold or whether it represents a breaking change for existing users.

🤖 Prompt for AI Agents
In packages/shared/src/utils/validation.ts around lines 19–20, add an inline
code comment above the prompt length check that states the rationale for the
4000-character limit: explicitly indicate whether the limit is required by a
downstream provider API (name the provider e.g., Gitee AI / HuggingFace /
ModelScope and include a source/link to their docs) or if it was chosen as an
internal performance/cost safeguard, and state whether changing it is a breaking
change for users; also add a brief CHANGELOG entry noting the enforced limit and
its rationale so users are informed.

Fixes pnpm workspace error in Cloudflare Pages deployment workflow.
The wrangler-action was failing with ERR_PNPM_ADDING_TO_ROOT when
trying to install wrangler in a monorepo setup.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented Dec 13, 2025

Deploying zenith-image-generator with  Cloudflare Pages  Cloudflare Pages

Latest commit: f0bae85
Status: ✅  Deploy successful!
Preview URL: https://dd796c5c.zenith-image-generator.pages.dev
Branch Preview URL: https://dev.zenith-image-generator.pages.dev

View logs

Update project name from 'z-image' to 'zenith-image-generator' to match
the existing Cloudflare Pages project.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

🚀 Preview Deployment

Status URL
✅ Ready https://22e6c022.zenith-image-generator.pages.dev

Commit: c7a89f0

@WuMingDao WuMingDao changed the title Dev feat: add ModelScope provider, rich metadata & comprehensive infra overhaul Dec 13, 2025
@WuMingDao WuMingDao merged commit 80afa80 into main Dec 13, 2025
18 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant