diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml deleted file mode 100644 index 1601b86c2e0f..000000000000 --- a/.github/workflows/docker-build.yml +++ /dev/null @@ -1,141 +0,0 @@ -name: Publish Docker image (Multi-arch) - -on: - push: - tags: - - '*' - - '!nightly*' - workflow_dispatch: - inputs: - tag: - description: 'Tag name to build (e.g., v0.10.8-alpha.3)' - required: true - type: string - -jobs: - build_single_arch: - name: Build & push (${{ matrix.arch }}) - strategy: - fail-fast: false - matrix: - include: - - arch: amd64 - platform: linux/amd64 - runner: ubuntu-latest - - arch: arm64 - platform: linux/arm64 - runner: ubuntu-24.04-arm - runs-on: ${{ matrix.runner }} - outputs: - tag: ${{ steps.version.outputs.tag }} - - permissions: - packages: write - contents: read - id-token: write - - steps: - - name: Check out - uses: actions/checkout@v4 - with: - fetch-depth: ${{ github.event_name == 'workflow_dispatch' && 0 || 1 }} - ref: ${{ github.event.inputs.tag || github.ref }} - - - name: Resolve tag & write VERSION - id: version - run: | - if [ -n "${{ github.event.inputs.tag }}" ]; then - TAG="${{ github.event.inputs.tag }}" - if ! git rev-parse "refs/tags/$TAG" >/dev/null 2>&1; then - echo "::error::Tag '$TAG' does not exist" - exit 1 - fi - else - TAG=${GITHUB_REF#refs/tags/} - fi - echo "TAG=${TAG}" >> $GITHUB_ENV - echo "tag=${TAG}" >> $GITHUB_OUTPUT - echo "${TAG}" > VERSION - echo "Building tag: ${TAG} for ${{ matrix.arch }}" - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - - name: Log in to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Extract metadata (labels) - id: meta - uses: docker/metadata-action@v5 - with: - images: calciumion/new-api - - - name: Build & push - id: build - uses: docker/build-push-action@v6 - with: - context: . - platforms: ${{ matrix.platform }} - push: true - tags: | - calciumion/new-api:${{ env.TAG }}-${{ matrix.arch }} - calciumion/new-api:latest-${{ matrix.arch }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max - provenance: mode=max - sbom: true - - - name: Install cosign - uses: sigstore/cosign-installer@v3 - - - name: Sign image with cosign - run: cosign sign --yes calciumion/new-api@${{ steps.build.outputs.digest }} - - - name: Image summary - run: | - echo "### Docker Image Digest (${{ matrix.arch }})" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - echo "calciumion/new-api:${TAG}-${{ matrix.arch }}" >> $GITHUB_STEP_SUMMARY - echo "${{ steps.build.outputs.digest }}" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - - create_manifests: - name: Create multi-arch manifests - needs: [build_single_arch] - runs-on: ubuntu-latest - if: startsWith(github.ref, 'refs/tags/') || github.event_name == 'workflow_dispatch' - - steps: - - name: Set version - run: echo "TAG=${{ needs.build_single_arch.outputs.tag }}" >> $GITHUB_ENV - - - name: Log in to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Create & push manifest (version) - run: | - docker buildx imagetools create \ - -t calciumion/new-api:${TAG} \ - calciumion/new-api:${TAG}-amd64 \ - calciumion/new-api:${TAG}-arm64 - - - name: Create & push manifest (latest) - run: | - docker buildx imagetools create \ - -t calciumion/new-api:latest \ - calciumion/new-api:latest-amd64 \ - calciumion/new-api:latest-arm64 - - - name: Manifest summary - run: | - echo "### Multi-arch Manifest" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - docker buildx imagetools inspect calciumion/new-api:${TAG} >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/docker-hub-user.yml b/.github/workflows/docker-hub-user.yml new file mode 100644 index 000000000000..a5f39429f595 --- /dev/null +++ b/.github/workflows/docker-hub-user.yml @@ -0,0 +1,46 @@ +name: Publish user Docker Hub image + +on: + push: + branches: + - main + workflow_dispatch: + +jobs: + build: + name: Build and push + runs-on: ubuntu-latest + permissions: + contents: read + + steps: + - name: Check out + uses: actions/checkout@v4 + + - name: Write VERSION + run: echo "main-${GITHUB_SHA::7}" > VERSION + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log in to Docker Hub + uses: docker/login-action@v3 + with: + username: yeranshuanghua + password: ${{ secrets.DOCKER_HUB }} + + - name: Build and push + uses: docker/build-push-action@v6 + with: + context: . + file: ./Dockerfile + platforms: linux/amd64,linux/arm64 + push: true + tags: | + yeranshuanghua/shuanghua-api:latest + yeranshuanghua/shuanghua-api:${{ github.sha }} + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.github/workflows/docker-image-branch.yml b/.github/workflows/docker-image-branch.yml deleted file mode 100644 index 8b7fd07381ad..000000000000 --- a/.github/workflows/docker-image-branch.yml +++ /dev/null @@ -1,170 +0,0 @@ -name: Publish Docker image (manual branch) - -on: - workflow_dispatch: - inputs: - branch: - description: "Branch name to build (e.g. alpha, nightly)" - required: true - type: string - -jobs: - prepare: - name: Prepare Docker tags - runs-on: ubuntu-latest - outputs: - branch: ${{ steps.version.outputs.branch }} - sha: ${{ steps.version.outputs.sha }} - tag_prefix: ${{ steps.version.outputs.tag_prefix }} - version: ${{ steps.version.outputs.version }} - permissions: - contents: read - steps: - - name: Check out branch - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - with: - fetch-depth: 1 - ref: ${{ inputs.branch }} - - - name: Resolve Docker tags - id: version - env: - BRANCH_NAME: ${{ inputs.branch }} - run: | - TAG_PREFIX=$(printf '%s' "$BRANCH_NAME" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9_.-]+/-/g; s/^[.-]+//; s/[.-]+$//') - TAG_PREFIX=${TAG_PREFIX:0:105} - TAG_PREFIX=$(printf '%s' "$TAG_PREFIX" | sed -E 's/[.-]+$//') - if [ -z "$TAG_PREFIX" ]; then - echo "::error::Branch '$BRANCH_NAME' cannot be converted to a valid Docker tag prefix" - exit 1 - fi - - SHA=$(git rev-parse HEAD) - SHORT_SHA=$(git rev-parse --short HEAD) - VERSION="${TAG_PREFIX}-$(date +'%Y%m%d')-${SHORT_SHA}" - - echo "branch=$BRANCH_NAME" >> "$GITHUB_OUTPUT" - echo "sha=$SHA" >> "$GITHUB_OUTPUT" - echo "tag_prefix=$TAG_PREFIX" >> "$GITHUB_OUTPUT" - echo "version=$VERSION" >> "$GITHUB_OUTPUT" - echo "Prepared Docker tags for $BRANCH_NAME at $SHORT_SHA" - - build_single_arch: - name: Build & push (${{ matrix.arch }}) [native] - needs: [prepare] - strategy: - fail-fast: false - matrix: - include: - - arch: amd64 - platform: linux/amd64 - runner: ubuntu-latest - - arch: arm64 - platform: linux/arm64 - runner: ubuntu-24.04-arm - runs-on: ${{ matrix.runner }} - permissions: - contents: read - id-token: write - steps: - - name: Check out branch - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - with: - fetch-depth: 1 - ref: ${{ needs.prepare.outputs.sha }} - - - name: Write VERSION - run: | - echo "${{ needs.prepare.outputs.version }}" > VERSION - echo "Publishing version: ${{ needs.prepare.outputs.version }} for ${{ matrix.arch }}" - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 - - - name: Log in to Docker Hub - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Extract metadata (labels) - id: meta - uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5 - with: - images: | - calciumion/new-api - - - name: Build & push single-arch - id: build - uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 - with: - context: . - platforms: ${{ matrix.platform }} - push: true - tags: | - calciumion/new-api:${{ needs.prepare.outputs.tag_prefix }}-${{ matrix.arch }} - calciumion/new-api:${{ needs.prepare.outputs.version }}-${{ matrix.arch }} - labels: ${{ steps.meta.outputs.labels }} - cache-from: type=gha - cache-to: type=gha,mode=max - provenance: mode=max - sbom: true - - - name: Install cosign - uses: sigstore/cosign-installer@398d4b0eeef1380460a10c8013a76f728fb906ac # v3 - - - name: Sign image with cosign - run: cosign sign --yes calciumion/new-api@${{ steps.build.outputs.digest }} - - - name: Output digest - run: | - echo "### Docker Image Digest (${{ matrix.arch }})" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - echo "calciumion/new-api:${{ needs.prepare.outputs.tag_prefix }}-${{ matrix.arch }}" >> $GITHUB_STEP_SUMMARY - echo "calciumion/new-api:${{ needs.prepare.outputs.version }}-${{ matrix.arch }}" >> $GITHUB_STEP_SUMMARY - echo "${{ steps.build.outputs.digest }}" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - - create_manifests: - name: Create multi-arch manifests (Docker Hub) - needs: [prepare, build_single_arch] - runs-on: ubuntu-latest - permissions: - id-token: write - steps: - - name: Log in to Docker Hub - uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Create & push manifest (Docker Hub - branch) - run: | - docker buildx imagetools create \ - -t calciumion/new-api:${{ needs.prepare.outputs.tag_prefix }} \ - calciumion/new-api:${{ needs.prepare.outputs.tag_prefix }}-amd64 \ - calciumion/new-api:${{ needs.prepare.outputs.tag_prefix }}-arm64 - - - name: Create & push manifest (Docker Hub - versioned) - run: | - docker buildx imagetools create \ - -t calciumion/new-api:${{ needs.prepare.outputs.version }} \ - calciumion/new-api:${{ needs.prepare.outputs.version }}-amd64 \ - calciumion/new-api:${{ needs.prepare.outputs.version }}-arm64 - - - name: Install cosign - uses: sigstore/cosign-installer@398d4b0eeef1380460a10c8013a76f728fb906ac # v3 - - - name: Sign manifests with cosign - run: | - cosign sign --yes calciumion/new-api:${{ needs.prepare.outputs.tag_prefix }} - cosign sign --yes calciumion/new-api:${{ needs.prepare.outputs.version }} - - - name: Output manifest digest - run: | - echo "### Multi-arch Manifest Digests" >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY - docker buildx imagetools inspect calciumion/new-api:${{ needs.prepare.outputs.tag_prefix }} >> $GITHUB_STEP_SUMMARY - echo "---" >> $GITHUB_STEP_SUMMARY - docker buildx imagetools inspect calciumion/new-api:${{ needs.prepare.outputs.version }} >> $GITHUB_STEP_SUMMARY - echo '```' >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/electron-build.yml b/.github/workflows/electron-build.yml deleted file mode 100644 index 20113e00fe6b..000000000000 --- a/.github/workflows/electron-build.yml +++ /dev/null @@ -1,141 +0,0 @@ -name: Build Electron App - -on: - push: - tags: - - '*' # Triggers on version tags like v1.0.0 - - '!*-*' # Ignore pre-release tags like v1.0.0-beta - - '!*-alpha*' # Ignore alpha tags like v1.0.0-alpha - workflow_dispatch: # Allows manual triggering - -jobs: - build: - strategy: - matrix: - # os: [macos-latest, windows-latest] - os: [windows-latest] - - runs-on: ${{ matrix.os }} - defaults: - run: - shell: bash - - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - - - name: Setup Bun - uses: oven-sh/setup-bun@v2 - with: - bun-version: latest - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20' - - - name: Setup Go - uses: actions/setup-go@v5 - with: - go-version: '>=1.25.1' - - - name: Build frontend - env: - CI: "" - NODE_OPTIONS: "--max-old-space-size=4096" - run: | - cd web - bun install - DISABLE_ESLINT_PLUGIN='true' VITE_REACT_APP_VERSION=$(git describe --tags) bun run build - cd .. - - # - name: Build Go binary (macos/Linux) - # if: runner.os != 'Windows' - # run: | - # go mod download - # go build -ldflags "-s -w -X 'new-api/common.Version=$(git describe --tags)' -extldflags '-static'" -o new-api - - - name: Build Go binary (Windows) - if: runner.os == 'Windows' - run: | - go mod download - go build -ldflags "-s -w -X 'new-api/common.Version=$(git describe --tags)'" -o new-api.exe - - - name: Update Electron version - run: | - cd electron - VERSION=$(git describe --tags) - VERSION=${VERSION#v} # Remove 'v' prefix if present - # Convert to valid semver: take first 3 components and convert rest to prerelease format - # e.g., 0.9.3-patch.1 -> 0.9.3-patch.1 - if [[ $VERSION =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)(.*)$ ]]; then - MAJOR=${BASH_REMATCH[1]} - MINOR=${BASH_REMATCH[2]} - PATCH=${BASH_REMATCH[3]} - REST=${BASH_REMATCH[4]} - - VERSION="$MAJOR.$MINOR.$PATCH" - - # If there's extra content, append it without adding -dev - if [[ -n "$REST" ]]; then - VERSION="$VERSION$REST" - fi - fi - npm version $VERSION --no-git-tag-version --allow-same-version - - - name: Install Electron dependencies - run: | - cd electron - npm install - - # - name: Build Electron app (macOS) - # if: runner.os == 'macOS' - # run: | - # cd electron - # npm run build:mac - # env: - # CSC_IDENTITY_AUTO_DISCOVERY: false # Skip code signing - - - name: Build Electron app (Windows) - if: runner.os == 'Windows' - run: | - cd electron - npm run build:win - - # - name: Upload artifacts (macOS) - # if: runner.os == 'macOS' - # uses: actions/upload-artifact@v4 - # with: - # name: macos-build - # path: | - # electron/dist/*.dmg - # electron/dist/*.zip - - - name: Upload artifacts (Windows) - if: runner.os == 'Windows' - uses: actions/upload-artifact@v4 - with: - name: windows-build - path: | - electron/dist/*.exe - - release: - needs: build - runs-on: ubuntu-latest - if: startsWith(github.ref, 'refs/tags/') - permissions: - contents: write - - steps: - - name: Download all artifacts - uses: actions/download-artifact@v4 - - - name: Upload to Release - uses: softprops/action-gh-release@v2 - with: - files: | - windows-build/* - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml deleted file mode 100644 index 2dcda35e676e..000000000000 --- a/.github/workflows/pr-check.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: PR Check - -permissions: - contents: read - issues: read - pull-requests: read - -on: - pull_request_target: - types: [opened, reopened] - -jobs: - pr-quality: - runs-on: ubuntu-latest - steps: - - uses: peakoss/anti-slop@v0.2.1 - with: - max-failures: 4 - require-description: true - - # require-linked-issue: false - blocked-terms: | - 🤖 Generated with Claude Code - - require-pr-template: true - strict-pr-template-sections: "✅ 提交前检查项 / Checklist" - - detect-spam-usernames: true - min-account-age: 30 - - failure-add-pr-labels: "pr-check-failed" - failure-pr-message: "感谢您的提交。由于该 PR 未遵循我们的贡献模板,且被识别为缺乏人工参与的纯 AI 生成内容 (AI Slop),我们将先予以关闭。我们更欢迎经过人工审核、验证并带有个人思考的贡献。如果您认为这其中存在误解,请回复告知。/ Thank you for your submission. This PR has been closed because it does not follow our contribution template and has been identified as purely AI-generated content (AI Slop) without meaningful human involvement. We prioritize contributions that are human-verified and reflect individual effort. If you believe this is a mistake, please let us know by replying to this comment." - close-pr: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 32bdefdddd3a..000000000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,186 +0,0 @@ -name: Release (Linux, macOS, Windows) -permissions: - contents: write - -on: - workflow_dispatch: - inputs: - name: - description: 'reason' - required: false - push: - tags: - - '*' - - '!*-alpha*' - -jobs: - linux: - name: Linux Release - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - with: - fetch-depth: 0 - - name: Determine Version - run: | - VERSION=$(git describe --tags) - echo "VERSION=$VERSION" >> $GITHUB_ENV - - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 - with: - bun-version: latest - - name: Build Frontend (default) - env: - CI: "" - run: | - cd web - bun install --frozen-lockfile - cd default - DISABLE_ESLINT_PLUGIN='true' VITE_REACT_APP_VERSION=$VERSION bun run build - cd ../.. - - name: Build Frontend (classic) - env: - CI: "" - run: | - cd web - bun install --filter ./classic --frozen-lockfile - cd classic - VITE_REACT_APP_VERSION=$VERSION bun run build - cd ../.. - - name: Set up Go - uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5 - with: - go-version: '>=1.25.1' - - name: Build Backend (amd64) - run: | - go mod download - go build -ldflags "-s -w -X 'new-api/common.Version=$VERSION' -extldflags '-static'" -o new-api-$VERSION - - name: Build Backend (arm64) - run: | - sudo apt-get update - DEBIAN_FRONTEND=noninteractive sudo apt-get install -y gcc-aarch64-linux-gnu - CC=aarch64-linux-gnu-gcc CGO_ENABLED=1 GOOS=linux GOARCH=arm64 go build -ldflags "-s -w -X 'new-api/common.Version=$VERSION' -extldflags '-static'" -o new-api-arm64-$VERSION - - name: Generate checksums - run: sha256sum new-api-* > checksums-linux.txt - - - name: Release - uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2 - if: startsWith(github.ref, 'refs/tags/') - with: - files: | - new-api-* - checksums-linux.txt - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - macos: - name: macOS Release - runs-on: macos-latest - steps: - - name: Checkout - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - with: - fetch-depth: 0 - - name: Determine Version - run: | - VERSION=$(git describe --tags) - echo "VERSION=$VERSION" >> $GITHUB_ENV - - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 - with: - bun-version: latest - - name: Build Frontend (default) - env: - CI: "" - NODE_OPTIONS: "--max-old-space-size=4096" - run: | - cd web - bun install --frozen-lockfile - cd default - DISABLE_ESLINT_PLUGIN='true' VITE_REACT_APP_VERSION=$VERSION bun run build - cd ../.. - - name: Build Frontend (classic) - env: - CI: "" - run: | - cd web - bun install --filter ./classic --frozen-lockfile - cd classic - VITE_REACT_APP_VERSION=$VERSION bun run build - cd ../.. - - name: Set up Go - uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5 - with: - go-version: '>=1.25.1' - - name: Build Backend - run: | - go mod download - go build -ldflags "-X 'new-api/common.Version=$VERSION'" -o new-api-macos-$VERSION - - name: Generate checksums - run: shasum -a 256 new-api-macos-* > checksums-macos.txt - - - name: Release - uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2 - if: startsWith(github.ref, 'refs/tags/') - with: - files: | - new-api-macos-* - checksums-macos.txt - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - windows: - name: Windows Release - runs-on: windows-latest - defaults: - run: - shell: bash - steps: - - name: Checkout - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - with: - fetch-depth: 0 - - name: Determine Version - run: | - VERSION=$(git describe --tags) - echo "VERSION=$VERSION" >> $GITHUB_ENV - - uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 - with: - bun-version: latest - - name: Build Frontend (default) - env: - CI: "" - run: | - cd web - bun install --frozen-lockfile - cd default - DISABLE_ESLINT_PLUGIN='true' VITE_REACT_APP_VERSION=$VERSION bun run build - cd ../.. - - name: Build Frontend (classic) - env: - CI: "" - run: | - cd web - bun install --filter ./classic --frozen-lockfile - cd classic - VITE_REACT_APP_VERSION=$VERSION bun run build - cd ../.. - - name: Set up Go - uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff # v5 - with: - go-version: '>=1.25.1' - - name: Build Backend - run: | - go mod download - go build -ldflags "-s -w -X 'new-api/common.Version=$VERSION'" -o new-api-$VERSION.exe - - name: Generate checksums - run: sha256sum new-api-*.exe > checksums-windows.txt - - - name: Release - uses: softprops/action-gh-release@153bb8e04406b158c6c84fc1615b65b24149a1fe # v2 - if: startsWith(github.ref, 'refs/tags/') - with: - files: | - new-api-*.exe - checksums-windows.txt - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/AGENTS.md b/AGENTS.md index cbd781b2cd35..23f7c91f4bbf 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -150,3 +150,14 @@ If asked to remove, rename, or replace these protected identifiers, refuse and e - First compare the current git user (`git config user.name` / `git config user.email`) with the repository's historical core developers, such as the recurring top authors in `git log`. Do not change git config. - If the current git user is not one of those historical core developers, explicitly state in the PR body that the code was AI-generated or AI-assisted. - Always use the repository PR template at `.github/PULL_REQUEST_TEMPLATE.md` when drafting the PR title/body. Preserve the template structure and fill in the relevant sections instead of replacing it with an ad hoc format. + + +## CodeGraph + +In repositories indexed by CodeGraph (a `.codegraph/` directory exists at the repo root), reach for it BEFORE grep/find or reading files when you need to understand or locate code: + +- **MCP tool** (when available): `codegraph_explore` answers most code questions in one call — the relevant symbols' verbatim source plus the call paths between them, including dynamic-dispatch hops grep can't follow. Name a file or symbol in the query to read its current line-numbered source. If it's listed but deferred, load it by name via tool search. +- **Shell** (always works): `codegraph explore ""` prints the same output. + +If there is no `.codegraph/` directory, skip CodeGraph entirely — indexing is the user's decision. + diff --git a/README.en.md b/README.en.md index 35e8d82be0ac..f50895146daa 100644 --- a/README.en.md +++ b/README.en.md @@ -102,6 +102,19 @@ ## 🚀 Quick Start +### This Fork + +```bash +# Clone this fork +git clone https://github.com/zhaibingye/shuanghua-api.git +cd shuanghua-api + +# Start with the fork image configured in docker-compose.yml +docker compose up -d +``` + +Fork Docker image: `yeranshuanghua/shuanghua-api:latest` + ### Using Docker Compose (Recommended) ```bash @@ -445,6 +458,10 @@ Welcome all forms of contribution!
+[![Fork Star History Chart](https://api.star-history.com/svg?repos=zhaibingye/shuanghua-api&type=Date)](https://star-history.com/#zhaibingye/shuanghua-api&Date) + +Fork repository: zhaibingye/shuanghua-api + [![Star History Chart](https://api.star-history.com/svg?repos=Calcium-Ion/new-api&type=Date)](https://star-history.com/#Calcium-Ion/new-api&Date)
diff --git a/README.fr.md b/README.fr.md index 1a6d8e4635fe..6b9a01b42479 100644 --- a/README.fr.md +++ b/README.fr.md @@ -107,6 +107,19 @@ ## 🚀 Démarrage rapide +### Ce Fork + +```bash +# Cloner ce fork +git clone https://github.com/zhaibingye/shuanghua-api.git +cd shuanghua-api + +# Démarrer avec l'image fork configurée dans docker-compose.yml +docker compose up -d +``` + +Image Docker du fork : `yeranshuanghua/shuanghua-api:latest` + ### Utilisation de Docker Compose (recommandé) ```bash @@ -462,6 +475,10 @@ Si les politiques de votre organisation ne permettent pas l'utilisation de logic
+[![Fork Star History Chart](https://api.star-history.com/svg?repos=zhaibingye/shuanghua-api&type=Date)](https://star-history.com/#zhaibingye/shuanghua-api&Date) + +Dépôt fork : zhaibingye/shuanghua-api + [![Graphique de l'historique des étoiles](https://api.star-history.com/svg?repos=Calcium-Ion/new-api&type=Date)](https://star-history.com/#Calcium-Ion/new-api&Date)
diff --git a/README.ja.md b/README.ja.md index e0702a7d4631..b423132a2c51 100644 --- a/README.ja.md +++ b/README.ja.md @@ -107,6 +107,19 @@ ## 🚀 クイックスタート +### この Fork + +```bash +# この fork をクローン +git clone https://github.com/zhaibingye/shuanghua-api.git +cd shuanghua-api + +# docker-compose.yml に設定された fork イメージで起動 +docker compose up -d +``` + +Fork Docker イメージ: `yeranshuanghua/shuanghua-api:latest` + ### Docker Composeを使用(推奨) ```bash @@ -462,6 +475,10 @@ docker run --name new-api -d --restart always \
+[![Fork Star History Chart](https://api.star-history.com/svg?repos=zhaibingye/shuanghua-api&type=Date)](https://star-history.com/#zhaibingye/shuanghua-api&Date) + +Fork リポジトリ: zhaibingye/shuanghua-api + [![スター履歴チャート](https://api.star-history.com/svg?repos=Calcium-Ion/new-api&type=Date)](https://star-history.com/#Calcium-Ion/new-api&Date)
diff --git a/README.md b/README.md index 65e3facdb24e..b88c6e9b539a 100644 --- a/README.md +++ b/README.md @@ -107,6 +107,19 @@ ## 🚀 Quick Start +### This Fork + +```bash +# Clone this fork +git clone https://github.com/zhaibingye/shuanghua-api.git +cd shuanghua-api + +# Start with the fork image configured in docker-compose.yml +docker compose up -d +``` + +Fork Docker image: `yeranshuanghua/shuanghua-api:latest` + ### Using Docker Compose (Recommended) ```bash @@ -471,6 +484,10 @@ If your organization's policies do not permit the use of AGPLv3-licensed softwar
+[![Fork Star History Chart](https://api.star-history.com/svg?repos=zhaibingye/shuanghua-api&type=Date)](https://star-history.com/#zhaibingye/shuanghua-api&Date) + +Fork repository: zhaibingye/shuanghua-api + [![Star History Chart](https://api.star-history.com/svg?repos=Calcium-Ion/new-api&type=Date)](https://star-history.com/#Calcium-Ion/new-api&Date)
diff --git a/README.zh_CN.md b/README.zh_CN.md index 33878843f365..37e5b5fc510d 100644 --- a/README.zh_CN.md +++ b/README.zh_CN.md @@ -107,6 +107,19 @@ ## 🚀 快速开始 +### 当前 Fork + +```bash +# 克隆当前 fork +git clone https://github.com/zhaibingye/shuanghua-api.git +cd shuanghua-api + +# 使用 docker-compose.yml 中配置的 fork 镜像启动 +docker compose up -d +``` + +当前 Fork Docker 镜像:`yeranshuanghua/shuanghua-api:latest` + ### 使用 Docker Compose(推荐) ```bash @@ -462,6 +475,10 @@ docker run --name new-api -d --restart always \
+[![Fork Star History Chart](https://api.star-history.com/svg?repos=zhaibingye/shuanghua-api&type=Date)](https://star-history.com/#zhaibingye/shuanghua-api&Date) + +当前 Fork 仓库:zhaibingye/shuanghua-api + [![Star History Chart](https://api.star-history.com/svg?repos=Calcium-Ion/new-api&type=Date)](https://star-history.com/#Calcium-Ion/new-api&Date)
diff --git a/README.zh_TW.md b/README.zh_TW.md index 0845d5acbb81..5a89f7027da0 100644 --- a/README.zh_TW.md +++ b/README.zh_TW.md @@ -107,6 +107,19 @@ ## 🚀 快速開始 +### 目前 Fork + +```bash +# 複製目前 fork +git clone https://github.com/zhaibingye/shuanghua-api.git +cd shuanghua-api + +# 使用 docker-compose.yml 中設定的 fork 鏡像啟動 +docker compose up -d +``` + +目前 Fork Docker 鏡像:`yeranshuanghua/shuanghua-api:latest` + ### 使用 Docker Compose(推薦) ```bash @@ -462,6 +475,10 @@ docker run --name new-api -d --restart always \
+[![Fork Star History Chart](https://api.star-history.com/svg?repos=zhaibingye/shuanghua-api&type=Date)](https://star-history.com/#zhaibingye/shuanghua-api&Date) + +目前 Fork 倉庫:zhaibingye/shuanghua-api + [![Star History Chart](https://api.star-history.com/svg?repos=Calcium-Ion/new-api&type=Date)](https://star-history.com/#Calcium-Ion/new-api&Date)
diff --git a/controller/billing.go b/controller/billing.go index f75f6819842e..209150962209 100644 --- a/controller/billing.go +++ b/controller/billing.go @@ -106,3 +106,45 @@ func GetUsage(c *gin.Context) { c.JSON(200, usage) return } + +func GetCredits(c *gin.Context) { + var quota int + var err error + var token *model.Token + if common.DisplayTokenStatEnabled { + tokenId := c.GetInt("token_id") + token, err = model.GetTokenById(tokenId) + if err == nil { + quota = token.RemainQuota + } + } else { + userId := c.GetInt("id") + quota, err = model.GetUserQuota(userId, false) + } + if err != nil { + openAIError := types.OpenAIError{ + Message: err.Error(), + Type: "new_api_error", + } + c.JSON(200, gin.H{ + "error": openAIError, + }) + return + } + amount := float64(quota) + switch operation_setting.GetQuotaDisplayType() { + case operation_setting.QuotaDisplayTypeCNY: + amount = amount / common.QuotaPerUnit * operation_setting.USDExchangeRate + case operation_setting.QuotaDisplayTypeTokens: + // tokens 保持原值 + default: + amount = amount / common.QuotaPerUnit + } + if token != nil && token.UnlimitedQuota { + amount = 100000000 + } + credits := OpenAICreditsResponse{} + credits.Data.TotalUsage = amount + c.JSON(200, credits) + return +} diff --git a/controller/billing_test.go b/controller/billing_test.go new file mode 100644 index 000000000000..72beffea9cc3 --- /dev/null +++ b/controller/billing_test.go @@ -0,0 +1,115 @@ +package controller + +import ( + "fmt" + "net/http" + "net/http/httptest" + "strings" + "testing" + + "github.com/QuantumNous/new-api/common" + "github.com/QuantumNous/new-api/model" + "github.com/QuantumNous/new-api/setting/operation_setting" + "github.com/gin-gonic/gin" + "github.com/glebarez/sqlite" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "gorm.io/gorm" +) + +type creditsResponse struct { + Data struct { + TotalUsage float64 `json:"total_usage"` + } `json:"data"` +} + +func setupBillingCreditsTestDB(t *testing.T) *gorm.DB { + t.Helper() + + gin.SetMode(gin.TestMode) + common.SetDatabaseTypes(common.DatabaseTypeSQLite, common.DatabaseTypeSQLite) + + oldRedisEnabled := common.RedisEnabled + oldDisplayTokenStatEnabled := common.DisplayTokenStatEnabled + oldQuotaDisplayType := operation_setting.GetGeneralSetting().QuotaDisplayType + common.RedisEnabled = false + common.DisplayTokenStatEnabled = true + operation_setting.GetGeneralSetting().QuotaDisplayType = operation_setting.QuotaDisplayTypeUSD + t.Cleanup(func() { + common.RedisEnabled = oldRedisEnabled + common.DisplayTokenStatEnabled = oldDisplayTokenStatEnabled + operation_setting.GetGeneralSetting().QuotaDisplayType = oldQuotaDisplayType + }) + + dsn := fmt.Sprintf("file:%s?mode=memory&cache=shared", strings.ReplaceAll(t.Name(), "/", "_")) + db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{}) + require.NoError(t, err) + model.DB = db + model.LOG_DB = db + + require.NoError(t, db.AutoMigrate(&model.User{}, &model.Token{})) + t.Cleanup(func() { + sqlDB, err := db.DB() + if err == nil { + _ = sqlDB.Close() + } + }) + + return db +} + +func requestCredits(t *testing.T, configureContext func(*gin.Context)) creditsResponse { + t.Helper() + + recorder := httptest.NewRecorder() + ctx, _ := gin.CreateTestContext(recorder) + ctx.Request = httptest.NewRequest(http.MethodGet, "/v1/credits", nil) + configureContext(ctx) + + GetCredits(ctx) + + require.Equal(t, http.StatusOK, recorder.Code) + + var response creditsResponse + require.NoError(t, common.Unmarshal(recorder.Body.Bytes(), &response)) + return response +} + +func TestGetCreditsUsesTokenRemainingQuotaWhenTokenStatsEnabled(t *testing.T) { + db := setupBillingCreditsTestDB(t) + + token := model.Token{ + Id: 12, + UserId: 34, + Key: "credits-token", + Status: common.TokenStatusEnabled, + RemainQuota: int(common.QuotaPerUnit * 2), + } + require.NoError(t, db.Create(&token).Error) + + response := requestCredits(t, func(ctx *gin.Context) { + ctx.Set("token_id", token.Id) + }) + + assert.Equal(t, 2.0, response.Data.TotalUsage) +} + +func TestGetCreditsUsesUserQuotaWhenTokenStatsDisabled(t *testing.T) { + db := setupBillingCreditsTestDB(t) + common.DisplayTokenStatEnabled = false + + user := model.User{ + Id: 56, + Username: "credits-user", + Password: "password123", + Status: common.UserStatusEnabled, + Quota: int(common.QuotaPerUnit * 4), + } + require.NoError(t, db.Create(&user).Error) + + response := requestCredits(t, func(ctx *gin.Context) { + ctx.Set("id", user.Id) + }) + + assert.Equal(t, 4.0, response.Data.TotalUsage) +} diff --git a/controller/channel-billing.go b/controller/channel-billing.go index 751ee3600ac9..ac290438ddbc 100644 --- a/controller/channel-billing.go +++ b/controller/channel-billing.go @@ -53,6 +53,14 @@ type OpenAIUsageResponse struct { TotalUsage float64 `json:"total_usage"` // unit: 0.01 dollar } +type OpenAICreditsResponse struct { + Data OpenAICreditsData `json:"data"` +} + +type OpenAICreditsData struct { + TotalUsage float64 `json:"total_usage"` // unit: dollar +} + type OpenAISBUsageResponse struct { Msg string `json:"msg"` Data *struct { diff --git a/controller/model.go b/controller/model.go index cc2b1effac31..8203d68e04c7 100644 --- a/controller/model.go +++ b/controller/model.go @@ -169,6 +169,25 @@ func buildOpenAIModel(modelName string, ownerByModel map[string]string) dto.Open return oaiModel } +func buildGeminiModel(model dto.OpenAIModels) dto.GeminiModel { + modelName := strings.TrimPrefix(model.Id, "models/") + methods := []string{"generateContent", "countTokens"} + if strings.HasPrefix(modelName, "text-embedding") || + strings.HasPrefix(modelName, "embedding") || + strings.HasPrefix(modelName, "gemini-embedding") { + methods = []string{"embedContent", "batchEmbedContents"} + } else if strings.HasPrefix(modelName, "imagen") { + methods = []string{"predict"} + } + + return dto.GeminiModel{ + Name: "models/" + modelName, + BaseModelId: modelName, + DisplayName: modelName, + SupportedGenerationMethods: methods, + } +} + type modelListGroups struct { userGroup string tokenGroup string @@ -297,14 +316,11 @@ func ListModels(c *gin.Context, modelType int) { case constant.ChannelTypeGemini: userGeminiModels := make([]dto.GeminiModel, len(userOpenAiModels)) for i, model := range userOpenAiModels { - userGeminiModels[i] = dto.GeminiModel{ - Name: model.Id, - DisplayName: model.Id, - } + userGeminiModels[i] = buildGeminiModel(model) } c.JSON(200, gin.H{ "models": userGeminiModels, - "nextPageToken": nil, + "nextPageToken": "", }) default: c.JSON(200, gin.H{ diff --git a/controller/model_list_test.go b/controller/model_list_test.go index 3d09956bfa8f..0edad9c70d7f 100644 --- a/controller/model_list_test.go +++ b/controller/model_list_test.go @@ -34,6 +34,11 @@ type userModelsResponse struct { Data []string `json:"data"` } +type geminiListModelsResponse struct { + Models []dto.GeminiModel `json:"models"` + NextPageToken string `json:"nextPageToken"` +} + func setupModelListControllerTestDB(t *testing.T) *gorm.DB { t.Helper() @@ -131,6 +136,16 @@ func withSelfUseModeDisabled(t *testing.T) { }) } +func withSelfUseModeEnabled(t *testing.T) { + t.Helper() + + original := operation_setting.SelfUseModeEnabled + operation_setting.SelfUseModeEnabled = true + t.Cleanup(func() { + operation_setting.SelfUseModeEnabled = original + }) +} + func decodeListModelsResponse(t *testing.T, recorder *httptest.ResponseRecorder) map[string]struct{} { t.Helper() @@ -165,6 +180,15 @@ func decodeUserModelsResponse(t *testing.T, recorder *httptest.ResponseRecorder) return payload.Data } +func decodeGeminiListModelsResponse(t *testing.T, recorder *httptest.ResponseRecorder) geminiListModelsResponse { + t.Helper() + + require.Equal(t, http.StatusOK, recorder.Code) + var payload geminiListModelsResponse + require.NoError(t, common.Unmarshal(recorder.Body.Bytes(), &payload)) + return payload +} + func TestGetUserModelsFiltersByRequestedGroup(t *testing.T) { db := setupModelListControllerTestDB(t) require.NoError(t, db.Create(&model.User{ @@ -288,8 +312,51 @@ func TestListModelsTokenLimitIncludesTieredBillingModel(t *testing.T) { require.NotContains(t, ids, "zz-token-unpriced-model") } +func TestListModelsGeminiReturnsNativeModelListShape(t *testing.T) { + withSelfUseModeEnabled(t) + + db := setupModelListControllerTestDB(t) + require.NoError(t, db.Create(&[]model.Ability{ + {Group: "default", Model: "gemini-2.5-flash", ChannelId: 1, Enabled: true}, + {Group: "default", Model: "gemini-embedding-001", ChannelId: 1, Enabled: true}, + }).Error) + + recorder := httptest.NewRecorder() + ctx, _ := gin.CreateTestContext(recorder) + ctx.Request = httptest.NewRequest(http.MethodGet, "/v1beta/models", nil) + common.SetContextKey(ctx, constant.ContextKeyUserGroup, "default") + + ListModels(ctx, constant.ChannelTypeGemini) + + payload := decodeGeminiListModelsResponse(t, recorder) + assert.Equal(t, "", payload.NextPageToken) + require.Len(t, payload.Models, 2) + + modelsByID := make(map[string]dto.GeminiModel, len(payload.Models)) + for _, item := range payload.Models { + modelsByID[item.BaseModelId] = item + } + + flash, ok := modelsByID["gemini-2.5-flash"] + require.True(t, ok) + assert.Equal(t, "models/gemini-2.5-flash", flash.Name) + assert.Equal(t, "gemini-2.5-flash", flash.DisplayName) + assert.Equal(t, []string{"generateContent", "countTokens"}, flash.SupportedGenerationMethods) + + embedding, ok := modelsByID["gemini-embedding-001"] + require.True(t, ok) + assert.Equal(t, "models/gemini-embedding-001", embedding.Name) + assert.Equal(t, []string{"embedContent", "batchEmbedContents"}, embedding.SupportedGenerationMethods) + + body := recorder.Body.String() + assert.NotContains(t, body, `"name":"gemini-2.5-flash"`) + assert.NotContains(t, body, `"supportedGenerationMethods":null`) + assert.NotContains(t, body, `"nextPageToken":null`) +} + func TestCheckUpdatePasswordRequiresCurrentPassword(t *testing.T) { db := setupModelListControllerTestDB(t) + hashedPassword, err := common.Password2Hash("CurrentPassword123") require.NoError(t, err) user := &model.User{ @@ -315,6 +382,7 @@ func TestCheckUpdatePasswordRequiresCurrentPassword(t *testing.T) { func TestCheckUpdatePasswordRejectsHistoricalEmptyPassword(t *testing.T) { db := setupModelListControllerTestDB(t) + user := &model.User{ Username: "legacy-passwordless-user", Password: "", @@ -365,3 +433,4 @@ func TestSetupLoginDoesNotTouchPasswordWhenPasswordFieldOmitted(t *testing.T) { require.NoError(t, db.First(&stored, user.Id).Error) assert.Equal(t, hashedPassword, stored.Password) } + diff --git a/docker-compose.yml b/docker-compose.yml index f5881f4a24cc..a10ede639344 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,7 +16,7 @@ version: '3.4' # For compatibility with older Docker versions services: new-api: - image: calciumion/new-api:latest + image: yeranshuanghua/shuanghua-api:latest container_name: new-api restart: always command: --log-dir /app/logs diff --git a/docs/installation/BT.md b/docs/installation/BT.md index 8579b3547250..e1a4454056c6 100644 --- a/docs/installation/BT.md +++ b/docs/installation/BT.md @@ -58,6 +58,7 @@ version: '3' services: new-api: image: calciumion/new-api:latest + # 当前 fork 可使用: yeranshuanghua/shuanghua-api:latest container_name: new-api restart: always ports: @@ -127,6 +128,8 @@ volumes: ```bash # 拉取最新镜像 docker pull calciumion/new-api:latest +# 当前 fork 镜像 +docker pull yeranshuanghua/shuanghua-api:latest # 重启容器 docker-compose down && docker-compose up -d @@ -140,6 +143,7 @@ docker-compose down && docker-compose up -d - [环境变量配置](https://docs.newapi.pro/zh/docs/installation/config-maintenance/environment-variables) - [常见问题](https://docs.newapi.pro/zh/docs/support/faq) - [GitHub 仓库](https://github.com/QuantumNous/new-api) +- [当前 Fork 仓库](https://github.com/zhaibingye/shuanghua-api) *** @@ -148,4 +152,3 @@ docker-compose down && docker-compose up -d ![宝塔面板 Docker 安装](https://github.com/user-attachments/assets/7a6fc03e-c457-45e4-b8f9-184508fc26b0) > ⚠️ 注意:密钥为环境变量 `SESSION_SECRET`,请务必设置! - diff --git a/dto/pricing.go b/dto/pricing.go index 1ed8dcd31c29..f8fe01d22021 100644 --- a/dto/pricing.go +++ b/dto/pricing.go @@ -19,17 +19,17 @@ type AnthropicModel struct { } type GeminiModel struct { - Name interface{} `json:"name"` - BaseModelId interface{} `json:"baseModelId"` - Version interface{} `json:"version"` - DisplayName interface{} `json:"displayName"` - Description interface{} `json:"description"` - InputTokenLimit interface{} `json:"inputTokenLimit"` - OutputTokenLimit interface{} `json:"outputTokenLimit"` - SupportedGenerationMethods []interface{} `json:"supportedGenerationMethods"` - Thinking interface{} `json:"thinking"` - Temperature interface{} `json:"temperature"` - MaxTemperature interface{} `json:"maxTemperature"` - TopP interface{} `json:"topP"` - TopK interface{} `json:"topK"` + Name string `json:"name,omitempty"` + BaseModelId string `json:"baseModelId,omitempty"` + Version string `json:"version,omitempty"` + DisplayName string `json:"displayName,omitempty"` + Description string `json:"description,omitempty"` + InputTokenLimit any `json:"inputTokenLimit,omitempty"` + OutputTokenLimit any `json:"outputTokenLimit,omitempty"` + SupportedGenerationMethods []string `json:"supportedGenerationMethods,omitempty"` + Thinking any `json:"thinking,omitempty"` + Temperature any `json:"temperature,omitempty"` + MaxTemperature any `json:"maxTemperature,omitempty"` + TopP any `json:"topP,omitempty"` + TopK any `json:"topK,omitempty"` } diff --git a/middleware/auth.go b/middleware/auth.go index 86abddc79945..f9a236479c49 100644 --- a/middleware/auth.go +++ b/middleware/auth.go @@ -338,6 +338,7 @@ func TokenAuth() func(c *gin.Context) { // gemini api 从query中获取key if strings.HasPrefix(c.Request.URL.Path, "/v1beta/models") || strings.HasPrefix(c.Request.URL.Path, "/v1beta/openai/models") || + c.Request.URL.Path == "/v1/models" || strings.HasPrefix(c.Request.URL.Path, "/v1/models/") { skKey := c.Query("key") if skKey != "" { diff --git a/relay/channel/gemini/relay-gemini.go b/relay/channel/gemini/relay-gemini.go index e39826dd64e7..d5aebaedcc1d 100644 --- a/relay/channel/gemini/relay-gemini.go +++ b/relay/channel/gemini/relay-gemini.go @@ -1731,8 +1731,8 @@ func FetchGeminiModels(baseURL, apiKey, proxyURL string) ([]string, error) { } for _, model := range modelsResponse.Models { - modelNameValue, ok := model.Name.(string) - if !ok { + modelNameValue := strings.TrimSpace(model.Name) + if modelNameValue == "" { continue } modelName := strings.TrimPrefix(modelNameValue, "models/") diff --git a/router/dashboard.go b/router/dashboard.go index 2e486156d92a..f720a5e15aef 100644 --- a/router/dashboard.go +++ b/router/dashboard.go @@ -19,5 +19,6 @@ func SetDashboardRouter(router *gin.Engine) { apiRouter.GET("/v1/dashboard/billing/subscription", controller.GetSubscription) apiRouter.GET("/dashboard/billing/usage", controller.GetUsage) apiRouter.GET("/v1/dashboard/billing/usage", controller.GetUsage) + apiRouter.GET("/v1/credits", controller.GetCredits) } } diff --git a/router/relay-router.go b/router/relay-router.go index 17a13cad7fd6..75cbc12f8e67 100644 --- a/router/relay-router.go +++ b/router/relay-router.go @@ -25,7 +25,7 @@ func SetRelayRouter(router *gin.Engine) { case c.GetHeader("x-api-key") != "" && c.GetHeader("anthropic-version") != "": controller.ListModels(c, constant.ChannelTypeAnthropic) case c.GetHeader("x-goog-api-key") != "" || c.Query("key") != "": // 单独的适配 - controller.RetrieveModel(c, constant.ChannelTypeGemini) + controller.ListModels(c, constant.ChannelTypeGemini) default: controller.ListModels(c, constant.ChannelTypeOpenAI) } diff --git a/web/classic/rsbuild.config.ts b/web/classic/rsbuild.config.ts index 3ccf1e96df8e..dfc12f962c65 100644 --- a/web/classic/rsbuild.config.ts +++ b/web/classic/rsbuild.config.ts @@ -10,6 +10,12 @@ const semiUiDir = path.resolve( path.dirname(require.resolve('@douyinfe/semi-ui')), '../..', ) +const semiFoundationRequire = createRequire( + require.resolve('@douyinfe/semi-foundation'), +) +const semiDateFnsDir = path.dirname( + semiFoundationRequire.resolve('date-fns/package.json'), +) export default defineConfig(({ envMode }) => { const env = loadEnv({ mode: envMode, prefixes: ['VITE_'] }) @@ -47,6 +53,7 @@ export default defineConfig(({ envMode }) => { semiUiDir, 'dist/css/semi.css', ), + 'date-fns': semiDateFnsDir, }, }, html: { diff --git a/web/classic/src/components/dashboard/DashboardHeader.jsx b/web/classic/src/components/dashboard/DashboardHeader.jsx index c2867e90c2a0..8153f13d0e97 100644 --- a/web/classic/src/components/dashboard/DashboardHeader.jsx +++ b/web/classic/src/components/dashboard/DashboardHeader.jsx @@ -27,9 +27,13 @@ const DashboardHeader = ({ showSearchModal, refresh, loading, + timeOptions, + dataExportDefaultTime, + onGranularityChange, t, }) => { const ICON_BUTTON_CLASS = 'text-white hover:bg-opacity-80 !rounded-full'; + const visibleTimeOptions = timeOptions || []; return (
@@ -39,7 +43,32 @@ const DashboardHeader = ({ > {getGreeting} -
+
+
+ {visibleTimeOptions.map((option) => { + const selected = option.value === dataExportDefaultTime; + return ( + + ); + })} +