Compare commits

..

74 Commits

Author SHA1 Message Date
admin de471d0fa0 refactor: move SidebarTrigger to header left, flatten system settings into sidebar
Docker Build / Build and Push Docker Image (push) Successful in 3m41s
2026-06-15 04:17:37 +08:00
admin ea5b152313 fix: TryUserAuth set role in context, fix docs page pagination response handling
Docker Build / Build and Push Docker Image (push) Successful in 3m48s
2026-06-15 04:07:15 +08:00
admin 75e608bdc8 refactor: sidebar full-height with logo+toggle at top, header beside sidebar
Docker Build / Build and Push Docker Image (push) Successful in 3m51s
2026-06-15 04:02:05 +08:00
admin c52ff411a7 fix: move SidebarTrigger to sidebar edge, move Dialog/AlertDialog outside SectionPageLayout
Docker Build / Build and Push Docker Image (push) Successful in 3m29s
2026-06-15 03:57:06 +08:00
admin ff407c4607 fix: stagger meteor delays for full-width appearance, move SidebarTrigger to AppHeader after logo
Docker Build / Build and Push Docker Image (push) Successful in 3m34s
2026-06-15 03:48:41 +08:00
admin ae380a5a81 fix: meteor trail full-width from start, fix dialog not rendering in doc management, move sidebar trigger after logo
Docker Build / Build and Push Docker Image (push) Successful in 3m25s
2026-06-15 03:15:21 +08:00
admin 71519301e4 fix: improve meteor effects and fix document management add functionality
Docker Build / Build and Push Docker Image (push) Successful in 3m45s
2026-06-15 03:01:39 +08:00
admin 497ed7c39a chore: empty commit to trigger CI
Docker Build / Build and Push Docker Image (push) Successful in 3m35s
2026-06-15 02:03:45 +08:00
admin 8be5131c29 style: update pricing page header to match docs page style with subtitle
Docker Build / Build and Push Docker Image (push) Successful in 3m30s
2026-06-15 01:56:39 +08:00
admin 6a9fd7611a fix: close overflow-hidden div properly in docs page JSX
Docker Build / Build and Push Docker Image (push) Successful in 4m26s
2026-06-15 01:31:24 +08:00
admin c42757f9af feat: add nebula clouds and meteor effects to pricing and docs pages, unify header style with homepage
Docker Build / Build and Push Docker Image (push) Failing after 1m37s
2026-06-15 01:26:15 +08:00
admin c450214d78 style: improve meteor effects with glowing head, gradient trail, and smoother animation
Docker Build / Build and Push Docker Image (push) Successful in 4m24s
2026-06-15 01:16:29 +08:00
admin 73e701e49a ci: trigger rebuild
Docker Build / Build and Push Docker Image (push) Successful in 4m33s
2026-06-15 01:02:37 +08:00
admin 537c292801 feat: add shooting star meteors and drifting nebula cloud effects to homepage
Docker Build / Build and Push Docker Image (push) Failing after 4m36s
2026-06-15 00:55:36 +08:00
admin b330253fe2 fix: add scrollbar-gutter:stable to prevent nav shift on page switch
Docker Build / Build and Push Docker Image (push) Successful in 4m36s
2026-06-15 00:51:23 +08:00
admin d703ccbb79 fix: unify PublicLayout usage with showMainContainer={false} across all pages to prevent nav shift
Docker Build / Build and Push Docker Image (push) Successful in 4m25s
2026-06-15 00:38:36 +08:00
admin 697ef3fcca fix: unify page container max-w and padding across all pages
Docker Build / Build and Push Docker Image (push) Successful in 4m18s
- Home CTA buttons: hover now fills with primary color for clear feedback
- Pricing/Docs/About: change px-4 to px-6 to match home page
- Wallet: change max-w-7xl to max-w-6xl to match nav
- Footer: change max-w-5xl to max-w-6xl for consistency
2026-06-15 00:05:12 +08:00
admin 1249eebf46 style: simplify pricing page header, update home CTA buttons to match filter chip style
Docker Build / Build and Push Docker Image (push) Successful in 4m33s
2026-06-14 23:07:13 +08:00
admin a34c284017 fix: adjust docs page layout to match other pages (max-w-6xl, pt-20, centered title)
Docker Build / Build and Push Docker Image (push) Successful in 4m43s
2026-06-14 21:50:22 +08:00
admin 3b2c0a0471 fix: resolve Gin route conflict between /docs/:slug and /docs/admin, move admin docs API to /admin/docs
Docker Build / Build and Push Docker Image (push) Successful in 4m33s
2026-06-14 21:40:48 +08:00
admin 207f98252d fix: docs nav always links to /docs, add admin GET routes for doc management, fix data parsing
Docker Build / Build and Push Docker Image (push) Failing after 4m1s
2026-06-14 21:32:55 +08:00
admin 113b9c8ecb fix: unify light/dark primary color, fix doc management API, remove compliance restriction
Docker Build / Build and Push Docker Image (push) Successful in 4m23s
2026-06-14 21:20:47 +08:00
admin a4c069f88d feat: redesign pricing page - move filters/search to top, remove sidebar, match homepage layout
Docker Build / Build and Push Docker Image (push) Successful in 4m21s
2026-06-14 20:51:14 +08:00
admin 7caf77db63 feat: remove Get Started arrow, redesign pricing table with Model/Group/Billing/PerRequest/Input/CachedInput/Output columns, default to table view
Docker Build / Build and Push Docker Image (push) Successful in 4m13s
2026-06-14 20:42:06 +08:00
admin 6faf989549 fix: remove header inner padding px-0
Docker Build / Build and Push Docker Image (push) Successful in 4m18s
2026-06-14 20:35:15 +08:00
admin 719d06ecd6 fix: consistent header height h-14, reduce side padding px-4
Docker Build / Build and Push Docker Image (push) Successful in 4m16s
2026-06-14 20:28:58 +08:00
admin 5464d14973 fix: header full-width, remove floating style, use border-bottom instead
Docker Build / Build and Push Docker Image (push) Successful in 4m17s
2026-06-14 20:21:50 +08:00
admin 446e1ce10b fix: move provider marquee after code demo section, center align
Docker Build / Build and Push Docker Image (push) Successful in 4m24s
2026-06-14 20:15:15 +08:00
admin cd8cdebdcb fix: move providers marquee between sections, restore footer, add notification to mobile
Docker Build / Build and Push Docker Image (push) Successful in 4m32s
2026-06-14 20:09:03 +08:00
admin 790d832756 fix: remove Codex from provider list, it's part of OpenAI/GPT
Docker Build / Build and Push Docker Image (push) Successful in 4m21s
2026-06-14 20:01:14 +08:00
admin 63c83d5abd fix: update docs_link description to mention built-in docs page
Docker Build / Build and Push Docker Image (push) Successful in 4m16s
2026-06-14 19:55:24 +08:00
admin 64326a86d5 refactor: homepage - remove demo section, adapt for token relay station, move providers to bottom
Docker Build / Build and Push Docker Image (push) Successful in 4m17s
2026-06-14 19:52:31 +08:00
admin 5c4ed6206e feat: redesign homepage, add docs page, admin doc/category management
Docker Build / Build and Push Docker Image (push) Successful in 4m15s
2026-06-14 19:34:55 +08:00
admin 43f9869246 fix: Termius light mode colors, remove social proof/features/CTA sections
Docker Build / Build and Push Docker Image (push) Successful in 4m27s
2026-06-14 18:53:29 +08:00
admin 8eb31a7c82 redesign: homepage layout referencing company project structure
Docker Build / Build and Push Docker Image (push) Successful in 4m19s
2026-06-14 18:44:58 +08:00
admin ca47fd18f5 fix: light mode primary to dark, cyan as accent via oklch classes
Docker Build / Build and Push Docker Image (push) Successful in 4m19s
2026-06-14 18:33:13 +08:00
admin 296120641a fix: light mode Termius theme + redesign homepage
Docker Build / Build and Push Docker Image (push) Successful in 4m10s
2026-06-14 18:24:49 +08:00
admin 5d18485e9a fix: resolve JSX quote mismatch and missing profile-dropdown import
Docker Build / Build and Push Docker Image (push) Successful in 4m25s
2026-06-14 18:14:36 +08:00
admin 60fc1eee31 fix: simplify home barrel export and use semantic tokens
Docker Build / Build and Push Docker Image (push) Failing after 1m39s
2026-06-14 18:08:41 +08:00
admin da8cf3eef0 fix: proper light/dark theme support for all public pages
Docker Build / Build and Push Docker Image (push) Failing after 1m31s
2026-06-14 17:42:31 +08:00
admin 1ddffd236c fix: force dark mode on auth/home, add grid texture and glow to homepage
Docker Build / Build and Push Docker Image (push) Successful in 4m3s
2026-06-14 17:27:06 +08:00
admin 5eeb3c9f18 fix: auth button hover, nav links, minimal homepage redesign
Docker Build / Build and Push Docker Image (push) Successful in 4m18s
2026-06-14 17:19:03 +08:00
admin 2d8bdc1b7c fix: auth buttons, dropdown ring, redesign homepage
Docker Build / Build and Push Docker Image (push) Successful in 4m33s
2026-06-14 16:49:59 +08:00
admin 2723bd66ad style: revert to cyan accent, fix dark hover button priority
Docker Build / Build and Push Docker Image (push) Successful in 4m15s
2026-06-14 16:13:53 +08:00
admin 2c4c2002c6 fix: light mode header button hover background
Docker Build / Build and Push Docker Image (push) Successful in 4m8s
2026-06-14 15:45:13 +08:00
admin 8cf49db0a8 fix: button hover visibility, card ring, homepage with code features
Docker Build / Build and Push Docker Image (push) Successful in 4m3s
2026-06-14 15:37:56 +08:00
admin 53a884a6fe design: Termius-dark theme - black bg, green accent, terminal style
Docker Build / Build and Push Docker Image (push) Successful in 4m6s
2026-06-14 15:23:40 +08:00
admin 60d63f87ff fix: light mode button colors, redesign homepage with features grid
Docker Build / Build and Push Docker Image (push) Successful in 4m13s
2026-06-14 15:05:00 +08:00
admin 603886d422 fix: correct primary colors, redesign hero, fix header text colors
Docker Build / Build and Push Docker Image (push) Successful in 4m7s
2026-06-14 14:40:08 +08:00
admin 3c056b00b1 design: apply Termius theme across all pages (light+dark)
Docker Build / Build and Push Docker Image (push) Successful in 4m8s
2026-06-14 13:53:31 +08:00
admin e9d408e718 design: redesign homepage strictly following Termius dark theme style
Docker Build / Build and Push Docker Image (push) Successful in 4m21s
2026-06-14 12:34:50 +08:00
admin c175a41a2b design: rewrite homepage with proper light/dark mode using Tailwind semantic tokens
Docker Build / Build and Push Docker Image (push) Successful in 4m23s
2026-06-14 12:20:39 +08:00
admin 4e335a9997 design: redesign default frontend homepage with Termius-dark style (black/blue/green)
Docker Build / Build and Push Docker Image (push) Successful in 4m23s
2026-06-14 11:51:56 +08:00
admin 6f1f8b60f1 design: redesign homepage with tech-minimalist style, remove blur-ball and shine-text
Docker Build / Build and Push Docker Image (push) Successful in 4m27s
2026-06-14 11:28:31 +08:00
admin a318279d5f feat: simplify SSH deploy step, update docker-compose to use own registry
Docker Build / Build and Push Docker Image (push) Failing after 3m56s
2026-06-14 11:22:50 +08:00
admin d1542a65ac feat: add SSH deploy step to CI/CD workflow
Docker Build / Build and Push Docker Image (push) Successful in 3m58s
2026-06-14 11:16:51 +08:00
admin 26ac8b5dc1 refactor: remove mt theme, clean up New API branding from classic frontend
Docker Build / Build and Push Docker Image (push) Successful in 3m54s
2026-06-14 11:06:47 +08:00
admin a8a96a7e60 redesign mt homepage with clean data-driven style, remove footer branding
Docker Build / Build and Push Docker Image (push) Successful in 4m16s
2026-06-14 00:10:06 +08:00
admin 45f676514a update favicon and logo for all themes
Docker Build / Build and Push Docker Image (push) Successful in 4m5s
2026-06-13 21:55:50 +08:00
admin 9e4ab6e7bb refactor: rename daisy frontend to mt, copy from classic
Docker Build / Build and Push Docker Image (push) Successful in 4m30s
2026-06-13 21:08:17 +08:00
admin 45aea22451 fix: resolve Gin route conflict between :slug and :id by moving doc admin routes to /docs/admin
Docker Build / Build and Push Docker Image (push) Successful in 4m36s
2026-06-13 12:29:21 +08:00
admin 5b9aa9e77a fix: specify varchar(255) for slug columns to fix MySQL unique index error
Docker Build / Build and Push Docker Image (push) Successful in 4m11s
2026-06-13 12:16:39 +08:00
admin 80bf73b41c fix: add missing logs pages and @hookform/resolvers dependency for Docker build
Docker Build / Build and Push Docker Image (push) Successful in 4m5s
2026-06-13 10:11:09 +08:00
admin e83ec743c8 feat: add DaisyUI frontend theme and document management system
Docker Build / Build and Push Docker Image (push) Failing after 1m35s
2026-06-13 01:36:06 +08:00
admin 824c6d9133 feat: add Tencent Cloud Coding Plan support and handle coding plan URLs in OpenAI adaptor
Docker Build / Build and Push Docker Image (push) Failing after 2m43s
2026-06-12 17:40:53 +08:00
admin 76f8112ee7 chore: remove payment compliance check system entirely
Docker Build / Build and Push Docker Image (push) Failing after 2m36s
2026-06-12 11:30:08 +08:00
admin 8afd1d1247 chore: bypass payment compliance check
Docker Build / Build and Push Docker Image (push) Successful in 3m35s
2026-06-12 10:12:33 +08:00
admin b4b58ee887 fix: re-apply brand rename with correct UTF-8 encoding (fixes Docker build)
Docker Build / Build and Push Docker Image (push) Successful in 3m50s
2026-06-12 04:57:18 +08:00
admin 97ddfeab59 chore: rebrand copyright from QuantumNous to modelstoken
Docker Build / Build and Push Docker Image (push) Failing after 1m2s
2026-06-12 01:05:46 +08:00
admin 6db071543c ci: add Docker cleanup step to prevent disk space exhaustion
Docker Build / Build and Push Docker Image (push) Successful in 4m41s
2026-06-12 00:12:00 +08:00
admin d83db15801 ci: fix docker-build workflow output and version handling
Docker Build / Build and Push Docker Image (push) Successful in 2m59s
2026-06-12 00:02:17 +08:00
admin 2780f71b8f ci: add main/master branch trigger to docker-build workflow
Docker Build / Build and Push Docker Image (push) Failing after 4m42s
2026-06-11 23:45:52 +08:00
admin 052da457eb ci: add Gitea Actions workflows 2026-06-11 23:22:08 +08:00
QuentinHsu 6f415428d3 perf(web): improve frontend table rendering and pinned columns/UI table (#5405)
* refactor(web): centralize data table implementation

- route all TanStack table setup through a shared data-table hook to remove repeated state and row model wiring.
- move table rendering, static table wrappers, empty states, and primitive exports behind the data-table module.
- update feature tables and configuration editors to share the same table UX while preserving their existing workflows.

* refactor(web): trim data table public API

- remove unused data-table exports and dead static table helper types.
- keep internal table header, skeleton, empty state, and faceted filter helpers private to the data-table module.
- route feature imports through the data-table barrel to avoid subpath coupling.

* refactor(web): unify table rendering components

- centralize static table headers, bodies, empty states, and shared class names behind the data-table package.
- migrate settings, pricing, channel, key, subscription, and model tables to the shared table APIs.
- remove data-table exports for low-level table primitives so feature code uses one supported abstraction.

* perf(web): keep list tables fixed within page content

- make shared data table pages fill available height and scroll row data inside the table body.
- add a fixed content layout mode so selected list pages avoid page-level scrolling.
- apply the fixed table behavior to keys, logs, channels, models, users, redemptions, and subscriptions.

* perf(web): refine table pagination controls

- show total row counts instead of redundant page range text.
- tighten visible page buttons so pagination fits constrained table widths.
- align pagination controls and tune text hierarchy for clearer scanning.

* perf(web): stabilize model pricing table columns

- keep model pricing columns at fixed widths so headers do not collapse in narrow layouts.
- truncate long model names and pricing summaries within their cells instead of squeezing adjacent columns.

* refactor(web): simplify data table rendering internals

- split table body rendering into focused helpers for loading, empty, and row states.
- extract static table row and cell class resolution to reduce branching in the main component.
- reuse a single pagination page-size option list to avoid duplicated constants.

* perf(pricing): reduce dynamic pricing table render work

- reuse dynamic pricing field metadata instead of rebuilding it inside table columns.
- precompute formatted dynamic prices per tier and group to avoid repeated entry mapping for each cell.
- simplify select option construction in related dialogs while preserving the same choices.

* refactor(web): streamline pricing table rendering

- reuse translated endpoint select options between trigger data and menu items.
- precompute dynamic pricing maps per group so table cells only resolve formatted values.
- add local dynamic pricing type aliases to keep helper signatures readable.

* refactor(web): merge pricing table imports

* refactor(web): merge upstream ratio table imports

* refactor(web): merge channel selector table imports

* refactor(web): simplify tiered pricing select items

* refactor(web): reuse model ratio row state

* refactor(web): rely on table view row defaults

* refactor(web): reuse pagination state values

* refactor(web): hoist pagination size select items

* refactor(web): clarify static table body rows

* refactor(web): extract table page pagination rendering

* fix(web): remove direct hast type dependency

- rely on Shiki transformer contextual typing for line nodes.
- allow frontend typecheck to pass without an undeclared hast package.

* refactor(web): trim data table hook return API

- return only the TanStack table instance from useDataTable.
- keep internal state handling private because callers do not consume it directly.

* refactor(web): keep static table empty row private

- stop exporting the internal StaticDataTableEmptyRow helper.
- keep the public static table API focused on the table component and column type.

* refactor(web): hide data table view props from barrel

* refactor(web): remove stale long text lint override

* fix(web): keep pinned table columns opaque

- apply pinned column background classes after custom column classes.
- use an opaque hover background so scrolled content cannot show through fixed cells.

* refactor(data-table): organize shared table components

- group table primitives, page composition, toolbar controls, static tables, and hooks by responsibility.
- split shared view types, row rendering, header rendering, and pinned-column styling out of the main table view.
- keep the public data-table barrel stable while documenting the new ownership boundaries.

* fix(web): stabilize split table column sizing

- derive default colgroup widths from visible columns when split headers or header sizing are enabled.
- apply a fixed table layout with computed minimum width so header and body columns stay aligned.
- keep split-header containers from leaking horizontal overflow and avoid extra pinned-column borders.

* fix(web): set stable table utility column widths

- assign fixed widths to selection columns so shared colgroup sizing keeps checkbox cells compact.
- size id columns in redemption and user tables to keep split headers aligned with body rows.

* fix(web): align model metadata icon cells

- render compact provider avatars in the metadata icon column instead of wide wordmarks.
- position icons in a fixed-size wrapper so they line up with the existing icon header alignment.

* fix(status-badge): hide status dot by default

* fix(web): prevent user invite info overlap

- give the invite info and created-at columns explicit widths so table sizing reserves enough space.
- allow invite badges to wrap within the cell instead of spilling into adjacent columns.

* perf(data-table): cache pinned column class resolution

- reuse the pinned column lookup while table props stay stable to reduce repeated per-render work.
- share the resolved column class handler across unified and split-header table layouts.
- localize page-number screen reader labels so pagination remains accessible in every locale.

* refactor(data-table): tighten static table modes

- make StaticDataTable distinguish data-driven and children-only usage through explicit prop shapes.
- remove unsupported columns-without-data fallback after confirming no repository callers rely on it.
- default manual table modes away from unused local row models to reduce repeated table work.

* fix(data-table): make pinned edit column opaque

- use an opaque muted background for the active action column so sticky cells do not reveal scrolled content underneath.

* fix(data-table): prevent narrow column overlap

- apply stable header sizing to remaining desktop data table pages so constrained layouts scroll instead of compressing cells.
- add explicit widths for key, quota, badge, and timestamp columns that contain fixed-format content.
- constrain masked values and timestamp cells with truncation to keep content inside its assigned column.

* fix(table): align table cell content with headers

- remove extra inline padding from masked table text buttons so values start at the cell edge.
- tag status badges and offset leading badges inside table cells to match header text alignment.

* fix(table): prevent admin list column overflow

- widen redemption and subscription table columns so masked codes, timestamps, and localized headers fit.
- localize subscription ID headers and add Received amount translations across supported locales.

* fix(provider-badge): unify provider icon spacing

- add a shared provider badge component for icon and status label layout.
- reuse it in channel type and model vendor columns so OpenAI icons align consistently.
2026-06-11 02:36:41 +08:00
1359 changed files with 7737 additions and 5201 deletions
+92
View File
@@ -0,0 +1,92 @@
name: Docker Build
on:
push:
branches:
- main
- master
tags:
- 'v*'
workflow_dispatch:
jobs:
build-and-push:
name: Build and Push Docker Image
runs-on: act-runner-4c6g
env:
RUNNER_TOOL_CACHE: /toolcache
steps:
- name: Install Docker CLI
run: |
if ! command -v docker &> /dev/null; then
if command -v apk &> /dev/null; then
apk add --no-cache docker-cli
elif command -v apt-get &> /dev/null; then
apt-get update && apt-get install -y docker.io
else
curl -fsSL https://download.docker.com/linux/static/stable/x86_64/docker-24.0.7.tgz | tar xz -C /tmp
mv /tmp/docker/docker /usr/local/bin/
chmod +x /usr/local/bin/docker
fi
fi
docker --version
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Resolve tag & write VERSION
id: version
run: |
if echo "${{ github.ref }}" | grep -q "^refs/tags/"; then
TAG=${GITHUB_REF#refs/tags/}
else
SHORT_SHA=$(git rev-parse --short HEAD)
TAG="dev-${SHORT_SHA}"
fi
echo "TAG=${TAG}" >> $GITHUB_ENV
echo "${TAG}" > VERSION
echo "Building tag: ${TAG}"
cat VERSION
- name: Login to Gitea Container Registry
run: |
echo "${{ secrets.PACKAGES_TOKEN }}" | docker login git.viaeon.com -u "${{ github.actor }}" --password-stdin
- name: Build Docker image
run: |
echo "Building image with tag: ${{ env.TAG }}"
docker build \
--label "org.opencontainers.image.source=https://git.viaeon.com/admin/new-api" \
--label "org.opencontainers.image.revision=${{ github.sha }}" \
-t git.viaeon.com/admin/new-api:${{ env.TAG }} \
-t git.viaeon.com/admin/new-api:latest .
- name: Push Docker image
run: |
echo "Pushing ${{ env.TAG }}..."
docker push git.viaeon.com/admin/new-api:${{ env.TAG }}
echo "Pushing latest..."
docker push git.viaeon.com/admin/new-api:latest
- name: Cleanup Docker
if: always()
run: |
echo "Removing local images..."
docker rmi git.viaeon.com/admin/new-api:${{ env.TAG }} git.viaeon.com/admin/new-api:latest 2>/dev/null || true
echo "Pruning unused Docker resources..."
docker system prune -af --volumes 2>/dev/null || true
echo "Docker disk usage:"
docker system df
- name: Deploy via SSH
if: success()
run: |
if [ -z "${{ secrets.DEPLOY_SSH_HOST }}" ]; then
echo "DEPLOY_SSH_HOST not set, skip deploy"
exit 0
fi
apk add --no-cache sshpass 2>/dev/null || apt-get update && apt-get install -y sshpass 2>/dev/null || true
sshpass -p "${{ secrets.DEPLOY_SSH_PASS }}" ssh -o StrictHostKeyChecking=no -p ${{ secrets.DEPLOY_SSH_PORT || 22 }} ${{ secrets.DEPLOY_SSH_USER }}@${{ secrets.DEPLOY_SSH_HOST }} "cd ${{ secrets.DEPLOY_DIR || '/opt/new-api' }} && docker compose pull && docker compose up -d"
+73
View File
@@ -0,0 +1,73 @@
name: Docker Build (alpha)
on:
push:
branches:
- alpha
workflow_dispatch:
jobs:
build-and-push:
name: Build and Push Alpha Docker Image
runs-on: act-runner-4c6g
env:
RUNNER_TOOL_CACHE: /toolcache
steps:
- name: Install Docker CLI
run: |
if ! command -v docker &> /dev/null; then
if command -v apk &> /dev/null; then
apk add --no-cache docker-cli
elif command -v apt-get &> /dev/null; then
apt-get update && apt-get install -y docker.io
else
curl -fsSL https://download.docker.com/linux/static/stable/x86_64/docker-24.0.7.tgz | tar xz -C /tmp
mv /tmp/docker/docker /usr/local/bin/
chmod +x /usr/local/bin/docker
fi
fi
docker --version
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Determine alpha version
id: version
run: |
VERSION="alpha-$(date +'%Y%m%d')-$(git rev-parse --short HEAD)"
echo "$VERSION" > VERSION
echo "VERSION=$VERSION" >> $GITHUB_ENV
echo "Publishing version: $VERSION"
- name: Login to Gitea Container Registry
run: |
echo "${{ secrets.PACKAGES_TOKEN }}" | docker login git.viaeon.com -u "${{ github.actor }}" --password-stdin 2>&1
- name: Build Docker image
run: |
echo "Building alpha image..."
docker build \
--label "org.opencontainers.image.source=https://git.viaeon.com/admin/new-api" \
--label "org.opencontainers.image.revision=${{ github.sha }}" \
-t git.viaeon.com/admin/new-api:${{ env.VERSION }} \
-t git.viaeon.com/admin/new-api:alpha . 2>&1
- name: Push Docker image
run: |
echo "Pushing ${{ env.VERSION }}..."
docker push git.viaeon.com/admin/new-api:${{ env.VERSION }}
echo "Pushing alpha..."
docker push git.viaeon.com/admin/new-api:alpha
- name: Cleanup Docker
if: always()
run: |
echo "Removing local images..."
docker rmi git.viaeon.com/admin/new-api:${{ env.VERSION }} git.viaeon.com/admin/new-api:alpha 2>/dev/null || true
echo "Pruning unused Docker resources..."
docker system prune -af --volumes 2>/dev/null || true
echo "Docker disk usage:"
docker system df
@@ -0,0 +1,73 @@
name: Docker Build (nightly)
on:
push:
branches:
- nightly
workflow_dispatch:
jobs:
build-and-push:
name: Build and Push Nightly Docker Image
runs-on: act-runner-4c6g
env:
RUNNER_TOOL_CACHE: /toolcache
steps:
- name: Install Docker CLI
run: |
if ! command -v docker &> /dev/null; then
if command -v apk &> /dev/null; then
apk add --no-cache docker-cli
elif command -v apt-get &> /dev/null; then
apt-get update && apt-get install -y docker.io
else
curl -fsSL https://download.docker.com/linux/static/stable/x86_64/docker-24.0.7.tgz | tar xz -C /tmp
mv /tmp/docker/docker /usr/local/bin/
chmod +x /usr/local/bin/docker
fi
fi
docker --version
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 1
- name: Determine nightly version
id: version
run: |
VERSION="nightly-$(date +'%Y%m%d')-$(git rev-parse --short HEAD)"
echo "$VERSION" > VERSION
echo "VERSION=$VERSION" >> $GITHUB_ENV
echo "Publishing version: $VERSION"
- name: Login to Gitea Container Registry
run: |
echo "${{ secrets.PACKAGES_TOKEN }}" | docker login git.viaeon.com -u "${{ github.actor }}" --password-stdin 2>&1
- name: Build Docker image
run: |
echo "Building nightly image..."
docker build \
--label "org.opencontainers.image.source=https://git.viaeon.com/admin/new-api" \
--label "org.opencontainers.image.revision=${{ github.sha }}" \
-t git.viaeon.com/admin/new-api:${{ env.VERSION }} \
-t git.viaeon.com/admin/new-api:nightly . 2>&1
- name: Push Docker image
run: |
echo "Pushing ${{ env.VERSION }}..."
docker push git.viaeon.com/admin/new-api:${{ env.VERSION }}
echo "Pushing nightly..."
docker push git.viaeon.com/admin/new-api:nightly
- name: Cleanup Docker
if: always()
run: |
echo "Removing local images..."
docker rmi git.viaeon.com/admin/new-api:${{ env.VERSION }} git.viaeon.com/admin/new-api:nightly 2>/dev/null || true
echo "Pruning unused Docker resources..."
docker system prune -af --volumes 2>/dev/null || true
echo "Docker disk usage:"
docker system df
+82
View File
@@ -0,0 +1,82 @@
name: PR Check
on:
pull_request:
types: [opened, reopened]
jobs:
pr-quality:
name: PR Quality Check
runs-on: act-runner-4c6g
env:
RUNNER_TOOL_CACHE: /toolcache
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Check PR description
env:
GITEA_TOKEN: ${{ secrets.PACKAGES_TOKEN }}
PR_NUMBER: ${{ github.event.pull_request.number }}
REPO: admin/new-api
GITEA_URL: https://git.viaeon.com
run: |
# 获取 PR 信息
PR_INFO=$(curl -s -H "Authorization: token ${GITEA_TOKEN}" \
"${GITEA_URL}/api/v1/repos/${REPO}/pulls/${PR_NUMBER}")
PR_BODY=$(echo "$PR_INFO" | jq -r '.body // empty')
PR_TITLE=$(echo "$PR_INFO" | jq -r '.title // empty')
PR_USER=$(echo "$PR_INFO" | jq -r '.user.login // empty')
FAILED=0
REASONS=""
# 检查 PR 描述是否为空
if [ -z "$PR_BODY" ] || [ "$PR_BODY" = "null" ]; then
FAILED=1
REASONS="${REASONS}- PR description is empty\n"
fi
# 检查 PR 标题是否为空
if [ -z "$PR_TITLE" ] || [ "$PR_TITLE" = "null" ]; then
FAILED=1
REASONS="${REASONS}- PR title is empty\n"
fi
# 检查是否包含纯 AI 生成标记
if echo "$PR_BODY" | grep -qi "Generated with Claude Code"; then
FAILED=1
REASONS="${REASONS}- PR appears to be purely AI-generated without meaningful human involvement\n"
fi
if [ "$FAILED" -eq 1 ]; then
echo "PR check failed:"
echo -e "$REASONS"
# 添加标签
curl -s -X POST \
"${GITEA_URL}/api/v1/repos/${REPO}/issues/${PR_NUMBER}/labels" \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"labels": ["pr-check-failed"]}'
# 添加评论
curl -s -X POST \
"${GITEA_URL}/api/v1/repos/${REPO}/issues/${PR_NUMBER}/comments" \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"body": "感谢您的提交。由于该 PR 未遵循我们的贡献模板,且被识别为缺乏人工参与的纯 AI 生成内容,我们将先予以关闭。我们更欢迎经过人工审核、验证并带有个人思考的贡献。如果您认为这其中存在误解,请回复告知。"}'
# 关闭 PR
curl -s -X PATCH \
"${GITEA_URL}/api/v1/repos/${REPO}/pulls/${PR_NUMBER}" \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"state": "closed"}'
exit 1
fi
echo "PR check passed!"
+161
View File
@@ -0,0 +1,161 @@
name: Release (Linux)
on:
push:
tags:
- 'v*'
- '!*-alpha*'
workflow_dispatch:
inputs:
tag:
description: 'Tag name to build (e.g., v0.10.8)'
required: true
type: string
jobs:
build-linux:
name: Linux Release
runs-on: act-runner-4c6g
env:
RUNNER_TOOL_CACHE: /toolcache
steps:
- name: Install dependencies
run: |
export PATH="/toolcache/bin:$PATH"
# Install Go
if ! command -v go &> /dev/null; then
curl -fsSL https://go.dev/dl/go1.25.1.linux-amd64.tar.gz | tar -C /usr/local -xzf -
echo "export PATH=\$PATH:/usr/local/go/bin" >> ~/.bashrc
export PATH=$PATH:/usr/local/go/bin
fi
go version
# Install Bun
if ! command -v bun &> /dev/null; then
curl -fsSL https://bun.sh/install | bash
export PATH="$HOME/.bun/bin:$PATH"
fi
bun --version
- name: Checkout repository
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Determine Version
run: |
if [ -n "${{ github.event.inputs.tag }}" ]; then
TAG="${{ github.event.inputs.tag }}"
else
TAG=${GITHUB_REF#refs/tags/}
fi
VERSION=$(git describe --tags 2>/dev/null || echo "$TAG")
echo "VERSION=$VERSION" >> $GITHUB_ENV
echo "Building version: $VERSION"
- name: Build Frontend (default)
env:
CI: ""
run: |
export PATH="$HOME/.bun/bin:/usr/local/go/bin:$PATH"
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: |
export PATH="$HOME/.bun/bin:/usr/local/go/bin:$PATH"
cd web
bun install --frozen-lockfile
cd classic
VITE_REACT_APP_VERSION=$VERSION bun run build
cd ../..
- name: Build Backend (amd64)
run: |
export PATH="/usr/local/go/bin:$PATH"
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: |
export PATH="/usr/local/go/bin:$PATH"
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: Upload artifacts
uses: actions/upload-artifact@v4
with:
name: linux-build
path: |
new-api-*
checksums-linux.txt
release:
name: Create Gitea Release
needs: [build-linux]
runs-on: act-runner-4c6g
env:
RUNNER_TOOL_CACHE: /toolcache
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Determine Version
run: |
if [ -n "${{ github.event.inputs.tag }}" ]; then
TAG="${{ github.event.inputs.tag }}"
else
TAG=${GITHUB_REF#refs/tags/}
fi
echo "TAG=$TAG" >> $GITHUB_ENV
- name: Download artifacts
uses: actions/download-artifact@v4
with:
path: artifacts
- name: Create Gitea Release
env:
GITEA_TOKEN: ${{ secrets.PACKAGES_TOKEN }}
run: |
# 使用 Gitea API 创建 Release
TAG="${{ env.TAG }}"
REPO="admin/new-api"
GITEA_URL="https://git.viaeon.com"
# 创建 Release
RELEASE_ID=$(curl -s -X POST \
"${GITEA_URL}/api/v1/repos/${REPO}/releases" \
-H "Authorization: token ${GITEA_TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"tag_name\": \"${TAG}\",
\"name\": \"${TAG}\",
\"body\": \"Release ${TAG}\",
\"draft\": false,
\"prerelease\": false
}" | jq -r '.id')
echo "Created release ID: ${RELEASE_ID}"
# 上传附件
find artifacts -type f | while read file; do
echo "Uploading: ${file}"
curl -s -X POST \
"${GITEA_URL}/api/v1/repos/${REPO}/releases/${RELEASE_ID}/assets" \
-H "Authorization: token ${GITEA_TOKEN}" \
-F "attachment=@${file}" \
-F "name=$(basename ${file})"
done
echo "Release ${TAG} created successfully!"
+2 -1
View File
@@ -7,9 +7,10 @@ upload
*.db
build
*.db-journal
logs
/logs
web/default/dist
web/classic/dist
web/daisy/dist
web/node_modules
web/dist
.env
+170
View File
@@ -0,0 +1,170 @@
# ModelsToken 管理平台 - 产品需求文档 (PRD)
## 1. 产品概述
ModelsToken 是一个 AI API 管理与分发平台,为开发者和企业提供统一的 AI 模型接入、密钥管理、用量计费、渠道代理等一站式服务。新前端将采用 React + DaisyUI 5 + TypeScript 构建,替换现有的 Default/Classic 双前端,同时新增本地文档管理功能。
- 目标用户:AI 应用开发者、企业运维人员、API 服务管理者
- 核心价值:简化 AI API 的管理复杂度,提供直观的操作界面和完整的文档支持
## 2. 核心功能
### 2.1 用户角色
| 角色 | 注册方式 | 核心权限 |
|------|----------|----------|
| 普通用户 | 用户名/邮箱/OAuth | 密钥管理、充值、订阅、日志查看、文档访问 |
| 管理员 | 由超级管理员指定 | 渠道管理、用户管理、兑换码、模型管理、订阅管理 |
| 超级管理员 | 系统初始化 | 全部权限 + 系统设置 |
### 2.2 功能模块
#### 公共页面(无需登录)
1. **首页**:Hero 区域、特性展示、快速入门指引
2. **登录页**:用户名/密码、OAuth 登录(GitHub/Discord/OIDC/LinuxDO/微信/Telegram/自定义)
3. **注册页**:注册表单 + Turnstile 人机验证
4. **忘记密码**:邮箱重置链接
5. **模型定价**:模型价格列表、搜索筛选
6. **关于页面**:项目信息、版本、许可证
7. **用户协议/隐私政策**
8. **初始化向导**:首次部署配置
#### 用户功能(需登录)
1. **仪表盘**:额度概览、使用趋势图、API 信息面板、公告、FAQ
2. **API 密钥管理**:创建/编辑/删除/批量操作、额度限制、模型限制、IP 限制
3. **钱包/充值**:余额查看、兑换码充值、在线支付(易支付/Stripe/Creem/Waffo)、签到
4. **订阅管理**:查看计划、购买订阅、当前订阅状态
5. **使用日志**:请求日志搜索/筛选、MJ 日志、任务日志、统计图表
6. **个人设置**:资料编辑、2FA 设置、Passkey 管理、OAuth 绑定、语言切换
7. **Playground**API 在线调试、Chat Completions 测试
8. **文档中心**(新增):本地文档管理、分类浏览、搜索、Markdown 渲染
#### 管理员功能
1. **渠道管理**:CRUD、测试、余额更新、标签管理、批量操作、多密钥、Codex OAuth、Ollama 管理
2. **用户管理**:列表/搜索/创建/编辑/升降级/启禁/额度调整
3. **兑换码管理**CRUD、批量删除无效码
4. **模型管理**:模型元数据 CRUD、上游同步、缺失模型检测
5. **供应商管理**CRUD
6. **订阅管理**:计划 CRUD、用户订阅管理
7. **部署管理**io.net 部署 CRUD、容器管理、日志
#### 超级管理员 - 系统设置
1. **站点设置**:名称/Logo/页脚/公告/首页内容/服务器地址
2. **认证设置**:注册/登录开关、OAuth 配置、Turnstile、Passkey、自定义 OAuth
3. **计费设置**:额度/倍率/支付配置/签到/分组倍率
4. **内容设置**:公告/FAQ/Uptime Kuma/聊天/绘图/Midjourney
5. **模型设置**:透传/思维模型/Gemini/Claude 配置
6. **运维设置**:重试/自动禁用/SMTP/性能监控/日志
7. **安全设置**:速率限制/敏感词/SSRF 防护/IP 过滤
### 2.3 新增功能 - 本地文档管理
| 功能 | 说明 |
|------|------|
| 文档分类 | 支持多级分类树,管理员可创建/编辑/删除分类 |
| 文档 CRUD | 管理员创建/编辑/删除文档,支持 Markdown 编辑器 |
| 文档浏览 | 用户按分类浏览文档,支持搜索 |
| 文档搜索 | 全文搜索文档标题和内容 |
| 文档版本 | 文档更新历史记录 |
| 权限控制 | 可设置文档为公开/登录可见/管理员可见 |
## 3. 核心流程
### 3.1 用户认证流程
```mermaid
flowchart TD
"访问平台" --> "已登录?"
"已登录?" -->|"是"| "仪表盘"
"已登录?" -->|"否"| "登录页"
"登录页" --> "输入凭证"
"输入凭证" --> "需要2FA?"
"需要2FA?" -->|"是"| "输入2FA码"
"需要2FA?" -->|"否"| "验证成功"
"输入2FA码" --> "验证成功"
"验证成功" --> "仪表盘"
"登录页" --> "OAuth登录"
"OAuth登录" --> "OAuth回调"
"OAuth回调" --> "已绑定账号?"
"已绑定账号?" -->|"是"| "仪表盘"
"已绑定账号?" -->|"否"| "绑定/注册"
```
### 3.2 API 调用流程
```mermaid
flowchart TD
"创建API密钥" --> "配置密钥参数"
"配置密钥参数" --> "使用密钥调用API"
"使用密钥调用API" --> "平台路由到渠道"
"平台路由到渠道" --> "返回结果"
"返回结果" --> "记录日志"
"记录日志" --> "扣除额度"
```
### 3.3 文档管理流程(新增)
```mermaid
flowchart TD
"管理员创建分类" --> "创建文档"
"创建文档" --> "Markdown编辑"
"Markdown编辑" --> "设置可见性"
"设置可见性" --> "发布文档"
"发布文档" --> "用户浏览/搜索"
```
## 4. 用户界面设计
### 4.1 设计风格
- **主色调**:深蓝 (#1e293b) + 亮蓝 (#3b82f6) 渐变,搭配 DaisyUI 的 `business` 主题
- **辅助色**:翡翠绿 (#10b981) 用于成功/在线状态,琥珀色 (#f59e0b) 用于警告
- **按钮风格**:DaisyUI 默认圆角按钮,主要操作用 `btn-primary`,危险操作用 `btn-error`
- **字体**JetBrains Mono(代码/密钥)+ Noto Sans SC(中文正文)
- **布局风格**:左侧固定导航栏 + 顶部状态栏 + 主内容区,响应式折叠
- **图标**Lucide React 图标库
- **动效**DaisyUI 内置动画 + 页面切换淡入
### 4.2 页面设计概览
| 页面 | 模块 | UI 元素 |
|------|------|---------|
| 首页 | Hero | 渐变背景、特性卡片、快速开始按钮 |
| 登录 | 表单 | 居中卡片、OAuth 按钮组、Turnstile |
| 仪表盘 | 统计卡片 | 4 列额度卡片、折线图、公告栏、API 信息 |
| 密钥管理 | 数据表 | 搜索栏、筛选器、表格、批量操作栏 |
| 渠道管理 | 数据表+表单 | 标签筛选、测试按钮、多密钥管理抽屉 |
| 系统设置 | 标签页 | 7 大分类侧边导航、表单分组、开关/输入框 |
| 文档中心 | 侧边树+内容 | 分类树导航、Markdown 渲染、搜索框、面包屑 |
| Playground | 分栏 | 左侧参数面板、右侧响应面板、模型选择器 |
### 4.3 响应式设计
- 桌面优先(1280px+
- 平板适配(768px-1279px):侧边栏折叠为抽屉
- 移动端适配(<768px):单列布局,表格改为卡片列表
### 4.4 布局结构
```
┌──────────────────────────────────────────────┐
│ 顶部导航栏 (Navbar) │
│ Logo | 搜索 | 通知 | 用户菜单 | 主题切换 │
├──────┬───────────────────────────────────────┤
│ │ │
│ 侧边 │ 主内容区 │
│ 导航 │ │
│ 栏 │ ┌─────────────────────────────────┐ │
│ │ │ 面包屑 + 页面标题 + 操作按钮 │ │
│ 仪表盘│ ├─────────────────────────────────┤ │
│ 密钥 │ │ │ │
│ 渠道 │ │ 页面内容 │ │
│ 用户 │ │ │ │
│ 日志 │ │ │ │
│ 钱包 │ └─────────────────────────────────┘ │
│ 订阅 │ │
│ 文档 │ │
│ 设置 │ │
│ │ │
└──────┴───────────────────────────────────────┘
```
+459
View File
@@ -0,0 +1,459 @@
# ModelsToken 管理平台 - 技术架构文档
## 1. 架构设计
```mermaid
flowchart TB
subgraph "前端 (React + DaisyUI 5)"
A["React 18"] --> B["React Router v6"]
B --> C["页面组件"]
C --> D["DaisyUI 5 组件"]
D --> E["Tailwind CSS 4"]
A --> F["Zustand 状态管理"]
A --> G["React Query 数据请求"]
A --> H["i18next 国际化"]
A --> I["React Markdown 渲染"]
end
subgraph "后端 (Go + Gin)"
J["Gin HTTP Server"]
J --> K["API 路由"]
J --> L["Relay 代理"]
K --> M["控制器"]
M --> N["模型层"]
N --> O["数据库 (SQLite/MySQL/PostgreSQL)"]
end
C -->|"Axios HTTP"| K
```
## 2. 技术说明
- **前端框架**React 18 + TypeScript
- **UI 库**DaisyUI 5 + Tailwind CSS 4
- **构建工具**Vite 6
- **路由**React Router v6(懒加载)
- **状态管理**Zustand(轻量级,替代 Redux
- **数据请求**TanStack React Query v5 + Axios
- **国际化**i18next + react-i18next
- **图表**Recharts
- **Markdown**react-markdown + remark-gfm + rehype-highlight
- **图标**Lucide React
- **代码高亮**highlight.js
- **表单验证**React Hook Form + Zod
- **通知**react-hot-toast
- **项目目录**`web/daisy/`
## 3. 路由定义
### 3.1 公共路由
| 路由 | 用途 |
|------|------|
| `/` | 首页 |
| `/login` | 登录 |
| `/register` | 注册 |
| `/forgot-password` | 忘记密码 |
| `/reset-password` | 密码重置确认 |
| `/setup` | 初始化向导 |
| `/pricing` | 模型定价 |
| `/about` | 关于 |
| `/user-agreement` | 用户协议 |
| `/privacy-policy` | 隐私政策 |
| `/oauth/callback/:provider` | OAuth 回调 |
### 3.2 认证后路由
| 路由 | 用途 |
|------|------|
| `/dashboard` | 仪表盘 |
| `/tokens` | API 密钥管理 |
| `/wallet` | 钱包/充值 |
| `/subscriptions` | 订阅管理 |
| `/logs` | 使用日志 |
| `/logs/midjourney` | MJ 日志 |
| `/logs/tasks` | 任务日志 |
| `/profile` | 个人设置 |
| `/playground` | Playground |
| `/docs` | 文档中心(新增) |
| `/docs/:slug` | 文档详情(新增) |
### 3.3 管理员路由
| 路由 | 用途 |
|------|------|
| `/admin/channels` | 渠道管理 |
| `/admin/users` | 用户管理 |
| `/admin/redemptions` | 兑换码管理 |
| `/admin/models` | 模型管理 |
| `/admin/vendors` | 供应商管理 |
| `/admin/deployments` | 部署管理 |
| `/admin/subscriptions` | 订阅计划管理 |
### 3.4 超级管理员路由
| 路由 | 用途 |
|------|------|
| `/settings/site` | 站点设置 |
| `/settings/auth` | 认证设置 |
| `/settings/billing` | 计费设置 |
| `/settings/content` | 内容设置 |
| `/settings/models` | 模型设置 |
| `/settings/operations` | 运维设置 |
| `/settings/security` | 安全设置 |
| `/settings/docs` | 文档管理(新增) |
## 4. API 定义
### 4.1 核心类型
```typescript
// 用户
interface User {
id: number;
username: string;
display_name: string;
email: string;
role: number; // 1=user, 10=admin, 100=root
status: number;
quota: number;
used_quota: number;
request_count: number;
group: string;
aff_code: string;
inviter_id: number;
language: string;
access_token: string;
created_time: number;
}
// 渠道
interface Channel {
id: number;
type: number;
key: string;
openai_organization?: string;
base_url: string;
models: string;
model_mapping?: string;
group: string;
groups: string[];
name: string;
priority: number;
weight: number;
status: number;
tag?: string;
setting?: string;
test_time: number;
response_time: number;
balance: number;
balance_updated_time: number;
created_time: number;
}
// 令牌
interface Token {
id: number;
user_id: number;
key: string;
status: number;
name: string;
created_time: number;
accessed_time: number;
expired_time: number;
remain_quota: number;
unlimited_quota: boolean;
used_quota: number;
models: string;
subnet: string;
group: string;
}
// 日志
interface Log {
id: number;
user_id: number;
created_at: number;
type: number;
content: string;
username: string;
token_name: string;
model_name: string;
quota: number;
prompt_tokens: number;
completion_tokens: number;
channel_id: number;
token_id: number;
group: string;
request_id: string;
ip: string;
detail: string;
}
// 订阅计划
interface SubscriptionPlan {
id: number;
name: string;
description: string;
price: number;
currency: string;
duration_days: number;
quota: number;
models: string;
enabled: boolean;
sort_order: number;
created_time: number;
}
// 文档(新增)
interface Document {
id: number;
title: string;
slug: string;
content: string; // Markdown
category_id: number;
category?: DocumentCategory;
visibility: 'public' | 'auth' | 'admin';
sort_order: number;
created_at: string;
updated_at: string;
author_id: number;
author?: User;
versions?: DocumentVersion[];
}
interface DocumentCategory {
id: number;
name: string;
slug: string;
parent_id: number | null;
children?: DocumentCategory[];
sort_order: number;
}
interface DocumentVersion {
id: number;
document_id: number;
content: string;
created_at: string;
author_id: number;
}
```
### 4.2 新增文档管理 API
| 端点 | 方法 | 权限 | 说明 |
|------|------|------|------|
| `/api/docs/categories` | GET | 公开 | 获取分类树 |
| `/api/docs/categories` | POST | Admin | 创建分类 |
| `/api/docs/categories/:id` | PUT | Admin | 更新分类 |
| `/api/docs/categories/:id` | DELETE | Admin | 删除分类 |
| `/api/docs/` | GET | 按可见性 | 文档列表(支持搜索) |
| `/api/docs/:slug` | GET | 按可见性 | 获取文档详情 |
| `/api/docs/` | POST | Admin | 创建文档 |
| `/api/docs/:id` | PUT | Admin | 更新文档 |
| `/api/docs/:id` | DELETE | Admin | 删除文档 |
| `/api/docs/:id/versions` | GET | Admin | 文档版本历史 |
## 5. 项目目录结构
```
web/daisy/
├── index.html
├── package.json
├── tsconfig.json
├── vite.config.ts
├── tailwind.config.ts
├── public/
│ └── manifest.json
└── src/
├── main.tsx # 入口
├── App.tsx # 根组件 + 路由
├── vite-env.d.ts
├── api/ # API 请求层
│ ├── client.ts # Axios 实例 + 拦截器
│ ├── auth.ts # 认证 API
│ ├── channel.ts # 渠道 API
│ ├── token.ts # 令牌 API
│ ├── user.ts # 用户 API
│ ├── log.ts # 日志 API
│ ├── subscription.ts # 订阅 API
│ ├── redemption.ts # 兑换码 API
│ ├── model.ts # 模型 API
│ ├── vendor.ts # 供应商 API
│ ├── deployment.ts # 部署 API
│ ├── option.ts # 系统设置 API
│ ├── payment.ts # 支付 API
│ └── doc.ts # 文档 API(新增)
├── stores/ # Zustand 状态
│ ├── auth.ts # 认证状态
│ └── ui.ts # UI 状态(侧边栏/主题)
├── hooks/ # 自定义 Hooks
│ ├── useAuth.ts
│ ├── usePermission.ts
│ └── useQuota.ts
├── components/ # 通用组件
│ ├── layout/
│ │ ├── AppLayout.tsx # 主布局
│ │ ├── Sidebar.tsx # 侧边导航
│ │ ├── Navbar.tsx # 顶部导航
│ │ └── Breadcrumb.tsx # 面包屑
│ ├── common/
│ │ ├── QuotaDisplay.tsx # 额度显示
│ │ ├── ModelBadge.tsx # 模型标签
│ │ ├── StatusBadge.tsx # 状态标签
│ │ ├── SearchInput.tsx # 搜索框
│ │ ├── DataTable.tsx # 数据表格
│ │ ├── ConfirmDialog.tsx # 确认对话框
│ │ └── LoadingSpinner.tsx # 加载动画
│ └── charts/
│ ├── QuotaChart.tsx # 额度趋势图
│ └── StatsChart.tsx # 统计图表
├── pages/ # 页面组件
│ ├── public/
│ │ ├── Home.tsx
│ │ ├── Login.tsx
│ │ ├── Register.tsx
│ │ ├── ForgotPassword.tsx
│ │ ├── Pricing.tsx
│ │ ├── About.tsx
│ │ └── Setup.tsx
│ ├── dashboard/
│ │ └── Dashboard.tsx
│ ├── tokens/
│ │ ├── TokenList.tsx
│ │ └── TokenForm.tsx
│ ├── channels/
│ │ ├── ChannelList.tsx
│ │ └── ChannelForm.tsx
│ ├── users/
│ │ ├── UserList.tsx
│ │ └── UserForm.tsx
│ ├── logs/
│ │ ├── LogList.tsx
│ │ ├── MidjourneyLog.tsx
│ │ └── TaskLog.tsx
│ ├── wallet/
│ │ └── Wallet.tsx
│ ├── subscriptions/
│ │ ├── PlanList.tsx
│ │ └── MySubscription.tsx
│ ├── redemptions/
│ │ └── RedemptionList.tsx
│ ├── models/
│ │ └── ModelList.tsx
│ ├── vendors/
│ │ └── VendorList.tsx
│ ├── deployments/
│ │ └── DeploymentList.tsx
│ ├── playground/
│ │ └── Playground.tsx
│ ├── profile/
│ │ └── Profile.tsx
│ ├── docs/ # 文档中心(新增)
│ │ ├── DocCenter.tsx # 文档浏览主页
│ │ ├── DocViewer.tsx # 文档阅读页
│ │ ├── DocEditor.tsx # 文档编辑页(管理员)
│ │ └── DocCategoryManager.tsx # 分类管理(管理员)
│ └── settings/
│ ├── SiteSettings.tsx
│ ├── AuthSettings.tsx
│ ├── BillingSettings.tsx
│ ├── ContentSettings.tsx
│ ├── ModelSettings.tsx
│ ├── OperationsSettings.tsx
│ ├── SecuritySettings.tsx
│ └── DocSettings.tsx # 文档设置(新增)
├── i18n/ # 国际化
│ ├── index.ts
│ └── locales/
│ ├── en.json
│ └── zh.json
├── lib/ # 工具函数
│ ├── constants.ts
│ ├── utils.ts
│ ├── quota.ts
│ └── channel-types.ts
└── types/ # TypeScript 类型
├── api.ts
├── channel.ts
├── token.ts
├── user.ts
├── log.ts
├── subscription.ts
├── doc.ts
└── option.ts
```
## 6. 数据模型(新增文档管理)
```mermaid
erDiagram
"document_categories" {
int id PK
string name
string slug UK
int parent_id FK
int sort_order
timestamp created_at
}
"documents" {
int id PK
string title
string slug UK
text content
int category_id FK
string visibility
int sort_order
int author_id FK
timestamp created_at
timestamp updated_at
}
"document_versions" {
int id PK
int document_id FK
text content
int author_id FK
timestamp created_at
}
"document_categories" ||--o{ "document_categories" : "parent"
"document_categories" ||--o{ "documents" : "has"
"documents" ||--o{ "document_versions" : "has"
```
### DDL
```sql
CREATE TABLE document_categories (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name VARCHAR(100) NOT NULL,
slug VARCHAR(100) NOT NULL UNIQUE,
parent_id INTEGER REFERENCES document_categories(id) ON DELETE SET NULL,
sort_order INTEGER DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE documents (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title VARCHAR(200) NOT NULL,
slug VARCHAR(200) NOT NULL UNIQUE,
content TEXT NOT NULL,
category_id INTEGER REFERENCES document_categories(id) ON DELETE SET NULL,
visibility VARCHAR(20) DEFAULT 'public' CHECK (visibility IN ('public', 'auth', 'admin')),
sort_order INTEGER DEFAULT 0,
author_id INTEGER NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE document_versions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
document_id INTEGER NOT NULL REFERENCES documents(id) ON DELETE CASCADE,
content TEXT NOT NULL,
author_id INTEGER NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX idx_documents_slug ON documents(slug);
CREATE INDEX idx_documents_category ON documents(category_id);
CREATE INDEX idx_documents_visibility ON documents(visibility);
CREATE INDEX idx_document_versions_doc ON document_versions(document_id);
```
+2 -2
View File
@@ -1,7 +1,7 @@
new-api Notices
new-api
Copyright (c) QuantumNous and contributors.
Copyright (c) modelstoken and contributors.
This project is licensed under the GNU Affero General Public License v3.0.
See LICENSE for the full project license terms.
@@ -19,7 +19,7 @@ Modified versions that present a user interface must also preserve a visible
link to the original project in a prominent about, legal, footer, or
attribution location:
https://github.com/QuantumNous/new-api
https://git.viaeon.com/admin/new-api
Modified versions must not misrepresent the origin of the software and must
mark their changes in accordance with AGPLv3 Section 7(c).
+1 -1
View File
@@ -14,7 +14,7 @@ import (
var StartTime = time.Now().Unix() // unit: second
var Version = "v0.0.0" // this hard coding will be replaced automatically when building, no need to manually change
var SystemName = "New API"
var SystemName = "ModelsToken"
var Footer = ""
var Logo = ""
var TopUpLink = ""
+8 -4
View File
@@ -51,17 +51,21 @@ type themeAwareFileSystem struct {
}
func (t *themeAwareFileSystem) Exists(prefix string, path string) bool {
if GetTheme() == "classic" {
switch GetTheme() {
case "classic":
return t.classicFS.Exists(prefix, path)
default:
return t.defaultFS.Exists(prefix, path)
}
return t.defaultFS.Exists(prefix, path)
}
func (t *themeAwareFileSystem) Open(name string) (http.File, error) {
if GetTheme() == "classic" {
switch GetTheme() {
case "classic":
return t.classicFS.Open(name)
default:
return t.defaultFS.Open(name)
}
return t.defaultFS.Open(name)
}
func NewThemeAwareFS(defaultFS, classicFS static.ServeFileSystem) static.ServeFileSystem {
+4
View File
@@ -206,4 +206,8 @@ var ChannelSpecialBases = map[string]ChannelSpecialBase{
ClaudeBaseURL: "https://ark.cn-beijing.volces.com/api/coding",
OpenAIBaseURL: "https://ark.cn-beijing.volces.com/api/coding/v3",
},
"tencent-coding-plan": {
ClaudeBaseURL: "https://api.lkeap.cloud.tencent.com/coding",
OpenAIBaseURL: "https://api.lkeap.cloud.tencent.com/coding/v3",
},
}
+5 -1
View File
@@ -312,7 +312,11 @@ func fetchChannelUpstreamModelIDs(channel *model.Channel) ([]string, error) {
url = fmt.Sprintf("%s/v1/models", baseURL)
}
default:
url = fmt.Sprintf("%s/v1/models", baseURL)
if plan, ok := constant.ChannelSpecialBases[baseURL]; ok && plan.OpenAIBaseURL != "" {
url = fmt.Sprintf("%s/models", plan.OpenAIBaseURL)
} else {
url = fmt.Sprintf("%s/v1/models", baseURL)
}
}
key, _, apiErr := channel.GetNextEnabledKey()
+256
View File
@@ -0,0 +1,256 @@
package controller
import (
"net/http"
"strconv"
"github.com/QuantumNous/new-api/common"
"github.com/QuantumNous/new-api/model"
"github.com/gin-gonic/gin"
)
// GetCategories 获取文档分类列表(公开)
func GetCategories(c *gin.Context) {
categories, err := model.GetDocumentCategories()
if err != nil {
common.ApiError(c, err)
return
}
common.ApiSuccess(c, categories)
}
// CreateCategory 创建文档分类(管理员)
func CreateCategory(c *gin.Context) {
var category model.DocumentCategory
if err := c.ShouldBindJSON(&category); err != nil {
common.ApiError(c, err)
return
}
if category.Name == "" {
common.ApiErrorMsg(c, "分类名称不能为空")
return
}
if category.Slug == "" {
common.ApiErrorMsg(c, "分类标识不能为空")
return
}
if err := model.CreateDocumentCategory(&category); err != nil {
common.ApiError(c, err)
return
}
common.ApiSuccess(c, &category)
}
// UpdateCategory 更新文档分类(管理员)
func UpdateCategory(c *gin.Context) {
id, err := strconv.Atoi(c.Param("id"))
if err != nil {
common.ApiError(c, err)
return
}
var category model.DocumentCategory
if err := c.ShouldBindJSON(&category); err != nil {
common.ApiError(c, err)
return
}
category.Id = id
if err := model.UpdateDocumentCategory(&category); err != nil {
common.ApiError(c, err)
return
}
common.ApiSuccess(c, &category)
}
// DeleteCategory 删除文档分类(管理员)
func DeleteCategory(c *gin.Context) {
id, err := strconv.Atoi(c.Param("id"))
if err != nil {
common.ApiError(c, err)
return
}
if err := model.DeleteDocumentCategory(id); err != nil {
common.ApiError(c, err)
return
}
common.ApiSuccess(c, nil)
}
// GetDocuments 获取文档列表(公开,根据认证状态过滤可见性)
func GetDocuments(c *gin.Context) {
keyword := c.Query("keyword")
categoryIdStr := c.Query("category_id")
var categoryId *int
if categoryIdStr != "" {
id, err := strconv.Atoi(categoryIdStr)
if err == nil {
categoryId = &id
}
}
pageInfo := common.GetPageQuery(c)
// 根据用户认证状态决定可见性过滤
visibility := c.Query("visibility")
role := c.GetInt("role")
var documents []*model.Document
var total int64
var err error
if role >= common.RoleAdminUser {
// 管理员可看所有
documents, total, err = model.GetDocuments(keyword, visibility, categoryId, pageInfo.GetStartIdx(), pageInfo.GetPageSize())
} else if role >= common.RoleCommonUser {
// 普通用户只能看 public 和 auth
if visibility == "public" || visibility == "auth" {
documents, total, err = model.GetDocuments(keyword, visibility, categoryId, pageInfo.GetStartIdx(), pageInfo.GetPageSize())
} else {
documents, total, err = model.GetDocumentsByVisibility(keyword, []string{"public", "auth"}, categoryId, pageInfo.GetStartIdx(), pageInfo.GetPageSize())
}
} else {
// 未登录用户只能看 public
documents, total, err = model.GetDocuments(keyword, "public", categoryId, pageInfo.GetStartIdx(), pageInfo.GetPageSize())
}
if err != nil {
common.ApiError(c, err)
return
}
pageInfo.SetTotal(int(total))
pageInfo.SetItems(documents)
common.ApiSuccess(c, pageInfo)
}
// GetDocument 获取单个文档(根据可见性检查权限)
func GetDocument(c *gin.Context) {
slug := c.Param("slug")
doc, err := model.GetDocumentBySlug(slug)
if err != nil {
common.ApiError(c, err)
return
}
// 检查可见性权限
role := c.GetInt("role")
switch doc.Visibility {
case "admin":
if role < common.RoleAdminUser {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "无权访问该文档",
})
return
}
case "auth":
if role < common.RoleCommonUser {
c.JSON(http.StatusOK, gin.H{
"success": false,
"message": "请先登录后查看该文档",
})
return
}
}
common.ApiSuccess(c, doc)
}
// CreateDocument 创建文档(管理员)
func CreateDocument(c *gin.Context) {
var doc model.Document
if err := c.ShouldBindJSON(&doc); err != nil {
common.ApiError(c, err)
return
}
if doc.Title == "" {
common.ApiErrorMsg(c, "文档标题不能为空")
return
}
if doc.Slug == "" {
common.ApiErrorMsg(c, "文档标识不能为空")
return
}
if doc.Content == "" {
common.ApiErrorMsg(c, "文档内容不能为空")
return
}
if doc.Visibility == "" {
doc.Visibility = "public"
}
doc.AuthorId = c.GetInt("id")
if err := model.CreateDocument(&doc); err != nil {
common.ApiError(c, err)
return
}
common.ApiSuccess(c, &doc)
}
// UpdateDocument 更新文档(管理员,自动创建版本记录)
func UpdateDocument(c *gin.Context) {
id, err := strconv.Atoi(c.Param("id"))
if err != nil {
common.ApiError(c, err)
return
}
var doc model.Document
if err := c.ShouldBindJSON(&doc); err != nil {
common.ApiError(c, err)
return
}
doc.Id = id
// 获取旧文档内容,自动创建版本记录
oldDoc, err := model.GetDocumentById(id)
if err != nil {
common.ApiError(c, err)
return
}
version := &model.DocumentVersion{
DocumentId: oldDoc.Id,
Content: oldDoc.Content,
AuthorId: oldDoc.AuthorId,
}
if err := model.CreateDocumentVersion(version); err != nil {
common.ApiError(c, err)
return
}
if err := model.UpdateDocument(&doc); err != nil {
common.ApiError(c, err)
return
}
common.ApiSuccess(c, &doc)
}
// DeleteDocument 删除文档(管理员)
func DeleteDocument(c *gin.Context) {
id, err := strconv.Atoi(c.Param("id"))
if err != nil {
common.ApiError(c, err)
return
}
if err := model.DeleteDocument(id); err != nil {
common.ApiError(c, err)
return
}
common.ApiSuccess(c, nil)
}
// GetDocumentVersions 获取文档版本历史(管理员)
func GetDocumentVersions(c *gin.Context) {
id, err := strconv.Atoi(c.Param("id"))
if err != nil {
common.ApiError(c, err)
return
}
pageInfo := common.GetPageQuery(c)
versions, total, err := model.GetDocumentVersions(id, pageInfo.GetStartIdx(), pageInfo.GetPageSize())
if err != nil {
common.ApiError(c, err)
return
}
pageInfo.SetTotal(int(total))
pageInfo.SetItems(versions)
common.ApiSuccess(c, pageInfo)
}
+1 -13
View File
@@ -7,7 +7,6 @@ import (
"strings"
"github.com/QuantumNous/new-api/common"
"github.com/QuantumNous/new-api/i18n"
"github.com/QuantumNous/new-api/model"
"github.com/QuantumNous/new-api/setting"
"github.com/QuantumNous/new-api/setting/console_setting"
@@ -29,10 +28,6 @@ var completionRatioMetaOptionKeys = []string{
"AudioCompletionRatio",
}
func isPaymentComplianceOptionKey(key string) bool {
return strings.HasPrefix(key, "payment_setting.compliance_")
}
func isPositiveOptionValue(value string) bool {
intValue, err := strconv.Atoi(strings.TrimSpace(value))
if err == nil {
@@ -139,15 +134,8 @@ func UpdateOption(c *gin.Context) {
}
switch option.Key {
case "QuotaForInviter", "QuotaForInvitee":
if isPositiveOptionValue(option.Value.(string)) && !operation_setting.IsPaymentComplianceConfirmed() {
common.ApiErrorI18n(c, i18n.MsgPaymentComplianceRequired)
return
}
// no compliance check needed
default:
if isPaymentComplianceOptionKey(option.Key) {
common.ApiErrorMsg(c, "合规确认字段不允许通过通用设置接口修改")
return
}
}
switch option.Key {
case "GitHubOAuthEnabled":
-82
View File
@@ -1,82 +0,0 @@
package controller
import (
"fmt"
"net/http"
"strconv"
"time"
"github.com/QuantumNous/new-api/common"
"github.com/QuantumNous/new-api/i18n"
"github.com/QuantumNous/new-api/logger"
"github.com/QuantumNous/new-api/model"
"github.com/QuantumNous/new-api/setting/operation_setting"
"github.com/gin-gonic/gin"
)
type PaymentComplianceRequest struct {
Confirmed bool `json:"confirmed"`
}
func requirePaymentCompliance(c *gin.Context) bool {
if !operation_setting.IsPaymentComplianceConfirmed() {
common.ApiErrorI18n(c, i18n.MsgPaymentComplianceRequired)
return false
}
return true
}
func ConfirmPaymentCompliance(c *gin.Context) {
if c.GetBool("use_access_token") {
c.JSON(http.StatusForbidden, gin.H{
"success": false,
"message": "This operation requires dashboard session authentication. API access token is not allowed.",
})
return
}
var req PaymentComplianceRequest
if err := common.DecodeJson(c.Request.Body, &req); err != nil {
common.ApiErrorMsg(c, "参数错误")
return
}
if !req.Confirmed {
common.ApiErrorMsg(c, "请确认合规声明")
return
}
now := time.Now().Unix()
userId := c.GetInt("id")
clientIP := c.ClientIP()
updates := map[string]string{
"payment_setting.compliance_confirmed": "true",
"payment_setting.compliance_terms_version": operation_setting.CurrentComplianceTermsVersion,
"payment_setting.compliance_confirmed_at": strconv.FormatInt(now, 10),
"payment_setting.compliance_confirmed_by": strconv.Itoa(userId),
"payment_setting.compliance_confirmed_ip": clientIP,
}
for key, value := range updates {
if err := model.UpdateOption(key, value); err != nil {
common.ApiError(c, err)
return
}
}
logger.LogInfo(c.Request.Context(), fmt.Sprintf(
"payment compliance confirmed user_id=%d ip=%s terms_version=%s confirmed_at=%d",
userId,
clientIP,
operation_setting.CurrentComplianceTermsVersion,
now,
))
common.ApiSuccess(c, gin.H{
"confirmed": true,
"terms_version": operation_setting.CurrentComplianceTermsVersion,
"confirmed_at": now,
"confirmed_by": userId,
})
}
@@ -7,14 +7,7 @@ import (
"github.com/QuantumNous/new-api/setting/operation_setting"
)
func isPaymentComplianceConfirmed() bool {
return operation_setting.IsPaymentComplianceConfirmed()
}
func isStripeTopUpEnabled() bool {
if !isPaymentComplianceConfirmed() {
return false
}
return strings.TrimSpace(setting.StripeApiSecret) != "" &&
strings.TrimSpace(setting.StripeWebhookSecret) != "" &&
strings.TrimSpace(setting.StripePriceId) != ""
@@ -29,9 +22,6 @@ func isStripeWebhookEnabled() bool {
}
func isCreemTopUpEnabled() bool {
if !isPaymentComplianceConfirmed() {
return false
}
products := strings.TrimSpace(setting.CreemProducts)
return strings.TrimSpace(setting.CreemApiKey) != "" &&
products != "" &&
@@ -47,9 +37,6 @@ func isCreemWebhookEnabled() bool {
}
func isWaffoTopUpEnabled() bool {
if !isPaymentComplianceConfirmed() {
return false
}
if !setting.WaffoEnabled {
return false
}
@@ -74,11 +61,6 @@ func isWaffoWebhookEnabled() bool {
}
func isWaffoPancakeTopUpEnabled() bool {
if !isPaymentComplianceConfirmed() {
return false
}
// Presence-of-credentials = enabled. Webhook public keys ship inside
// the SDK; mode (test/prod) is read from each event.
return strings.TrimSpace(setting.WaffoPancakeMerchantID) != "" &&
strings.TrimSpace(setting.WaffoPancakePrivateKey) != "" &&
strings.TrimSpace(setting.WaffoPancakeProductID) != ""
@@ -93,9 +75,6 @@ func isWaffoPancakeWebhookEnabled() bool {
}
func isEpayTopUpEnabled() bool {
if !isPaymentComplianceConfirmed() {
return false
}
return isEpayWebhookConfigured() && len(operation_setting.PayMethods) > 0
}
@@ -8,21 +8,7 @@ import (
"github.com/stretchr/testify/require"
)
func confirmPaymentComplianceForTest(t *testing.T) {
t.Helper()
paymentSetting := operation_setting.GetPaymentSetting()
originalConfirmed := paymentSetting.ComplianceConfirmed
originalTermsVersion := paymentSetting.ComplianceTermsVersion
t.Cleanup(func() {
paymentSetting.ComplianceConfirmed = originalConfirmed
paymentSetting.ComplianceTermsVersion = originalTermsVersion
})
paymentSetting.ComplianceConfirmed = true
paymentSetting.ComplianceTermsVersion = operation_setting.CurrentComplianceTermsVersion
}
func TestStripeWebhookEnabledRequiresTopUpAndWebhookConfig(t *testing.T) {
confirmPaymentComplianceForTest(t)
originalAPISecret := setting.StripeApiSecret
originalWebhookSecret := setting.StripeWebhookSecret
originalPriceID := setting.StripePriceId
@@ -45,7 +31,6 @@ func TestStripeWebhookEnabledRequiresTopUpAndWebhookConfig(t *testing.T) {
}
func TestCreemWebhookEnabledRequiresTopUpAndWebhookConfig(t *testing.T) {
confirmPaymentComplianceForTest(t)
originalAPIKey := setting.CreemApiKey
originalProducts := setting.CreemProducts
originalWebhookSecret := setting.CreemWebhookSecret
@@ -68,7 +53,6 @@ func TestCreemWebhookEnabledRequiresTopUpAndWebhookConfig(t *testing.T) {
}
func TestWaffoWebhookEnabledRequiresTopUpAndWebhookConfig(t *testing.T) {
confirmPaymentComplianceForTest(t)
originalEnabled := setting.WaffoEnabled
originalSandbox := setting.WaffoSandbox
originalAPIKey := setting.WaffoApiKey
@@ -113,7 +97,6 @@ func TestWaffoWebhookEnabledRequiresTopUpAndWebhookConfig(t *testing.T) {
}
func TestWaffoPancakeWebhookEnabledRequiresTopUpAndWebhookConfig(t *testing.T) {
confirmPaymentComplianceForTest(t)
originalMerchantID := setting.WaffoPancakeMerchantID
originalPrivateKey := setting.WaffoPancakePrivateKey
originalProductID := setting.WaffoPancakeProductID
@@ -123,9 +106,6 @@ func TestWaffoPancakeWebhookEnabledRequiresTopUpAndWebhookConfig(t *testing.T) {
setting.WaffoPancakeProductID = originalProductID
})
// Presence of all three credentials enables the gateway. Webhook public
// keys are bundled in the SDK and there is no separate Enabled toggle —
// clear any of the three fields to disable.
setting.WaffoPancakeMerchantID = ""
setting.WaffoPancakePrivateKey = "private"
setting.WaffoPancakeProductID = "product"
@@ -143,7 +123,6 @@ func TestWaffoPancakeWebhookEnabledRequiresTopUpAndWebhookConfig(t *testing.T) {
}
func TestEpayWebhookEnabledRequiresTopUpAndWebhookConfig(t *testing.T) {
confirmPaymentComplianceForTest(t)
originalPayAddress := operation_setting.PayAddress
originalEpayID := operation_setting.EpayId
originalEpayKey := operation_setting.EpayKey
-6
View File
@@ -8,7 +8,6 @@ import (
"github.com/QuantumNous/new-api/common"
"github.com/QuantumNous/new-api/i18n"
"github.com/QuantumNous/new-api/model"
"github.com/QuantumNous/new-api/setting/operation_setting"
"github.com/gin-gonic/gin"
)
@@ -60,11 +59,6 @@ func GetRedemption(c *gin.Context) {
}
func AddRedemption(c *gin.Context) {
if !operation_setting.IsPaymentComplianceConfirmed() {
common.ApiErrorI18n(c, i18n.MsgPaymentComplianceRequired)
return
}
redemption := model.Redemption{}
err := c.ShouldBindJSON(&redemption)
if err != nil {
-30
View File
@@ -6,7 +6,6 @@ import (
"github.com/QuantumNous/new-api/common"
"github.com/QuantumNous/new-api/model"
"github.com/QuantumNous/new-api/setting/operation_setting"
"github.com/QuantumNous/new-api/setting/ratio_setting"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
@@ -29,11 +28,6 @@ type SubscriptionBalancePayRequest struct {
// ---- User APIs ----
func GetSubscriptionPlans(c *gin.Context) {
if !operation_setting.IsPaymentComplianceConfirmed() {
common.ApiSuccess(c, []SubscriptionPlanDTO{})
return
}
var plans []model.SubscriptionPlan
if err := model.DB.Where("enabled = ?", true).Order("sort_order desc, id desc").Find(&plans).Error; err != nil {
common.ApiError(c, err)
@@ -98,10 +92,6 @@ func UpdateSubscriptionPreference(c *gin.Context) {
}
func SubscriptionRequestBalancePay(c *gin.Context) {
if !requirePaymentCompliance(c) {
return
}
userId := c.GetInt("id")
var req SubscriptionBalancePayRequest
if err := c.ShouldBindJSON(&req); err != nil || req.PlanId <= 0 {
@@ -139,10 +129,6 @@ type AdminUpsertSubscriptionPlanRequest struct {
}
func AdminCreateSubscriptionPlan(c *gin.Context) {
if !requirePaymentCompliance(c) {
return
}
var req AdminUpsertSubscriptionPlanRequest
if err := c.ShouldBindJSON(&req); err != nil {
common.ApiErrorMsg(c, "参数错误")
@@ -204,10 +190,6 @@ func AdminCreateSubscriptionPlan(c *gin.Context) {
}
func AdminUpdateSubscriptionPlan(c *gin.Context) {
if !requirePaymentCompliance(c) {
return
}
id, _ := strconv.Atoi(c.Param("id"))
if id <= 0 {
common.ApiErrorMsg(c, "无效的ID")
@@ -305,10 +287,6 @@ type AdminUpdateSubscriptionPlanStatusRequest struct {
}
func AdminUpdateSubscriptionPlanStatus(c *gin.Context) {
if !requirePaymentCompliance(c) {
return
}
id, _ := strconv.Atoi(c.Param("id"))
if id <= 0 {
common.ApiErrorMsg(c, "无效的ID")
@@ -333,10 +311,6 @@ type AdminBindSubscriptionRequest struct {
}
func AdminBindSubscription(c *gin.Context) {
if !requirePaymentCompliance(c) {
return
}
var req AdminBindSubscriptionRequest
if err := c.ShouldBindJSON(&req); err != nil || req.UserId <= 0 || req.PlanId <= 0 {
common.ApiErrorMsg(c, "参数错误")
@@ -376,10 +350,6 @@ type AdminCreateUserSubscriptionRequest struct {
// AdminCreateUserSubscription creates a new user subscription from a plan (no payment).
func AdminCreateUserSubscription(c *gin.Context) {
if !requirePaymentCompliance(c) {
return
}
userId, _ := strconv.Atoi(c.Param("id"))
if userId <= 0 {
common.ApiErrorMsg(c, "无效的用户ID")
-4
View File
@@ -21,10 +21,6 @@ type SubscriptionCreemPayRequest struct {
}
func SubscriptionRequestCreemPay(c *gin.Context) {
if !requirePaymentCompliance(c) {
return
}
var req SubscriptionCreemPayRequest
// Keep body for debugging consistency (like RequestCreemPay)
-4
View File
@@ -22,10 +22,6 @@ type SubscriptionEpayPayRequest struct {
}
func SubscriptionRequestEpay(c *gin.Context) {
if !requirePaymentCompliance(c) {
return
}
var req SubscriptionEpayPayRequest
if err := c.ShouldBindJSON(&req); err != nil || req.PlanId <= 0 {
common.ApiErrorMsg(c, "参数错误")
@@ -21,10 +21,6 @@ type SubscriptionStripePayRequest struct {
}
func SubscriptionRequestStripePay(c *gin.Context) {
if !requirePaymentCompliance(c) {
return
}
var req SubscriptionStripePayRequest
if err := c.ShouldBindJSON(&req); err != nil || req.PlanId <= 0 {
common.ApiErrorMsg(c, "参数错误")
@@ -21,10 +21,6 @@ type SubscriptionWaffoPancakePayRequest struct {
}
func SubscriptionRequestWaffoPancakePay(c *gin.Context) {
if !requirePaymentCompliance(c) {
return
}
var req SubscriptionWaffoPancakePayRequest
if err := c.ShouldBindJSON(&req); err != nil || req.PlanId <= 0 {
common.ApiErrorMsg(c, "参数错误")
+2 -8
View File
@@ -22,13 +22,8 @@ import (
)
func GetTopUpInfo(c *gin.Context) {
complianceConfirmed := operation_setting.IsPaymentComplianceConfirmed()
// 获取支付方式
payMethods := operation_setting.PayMethods
if !complianceConfirmed {
payMethods = []map[string]string{}
}
// 如果启用了 Stripe 支付,添加到支付方法列表
if isStripeTopUpEnabled() {
@@ -101,9 +96,8 @@ func GetTopUpInfo(c *gin.Context) {
"enable_creem_topup": isCreemTopUpEnabled(),
"enable_waffo_topup": enableWaffo,
"enable_waffo_pancake_topup": enableWaffoPancake,
"enable_redemption": complianceConfirmed,
"payment_compliance_confirmed": complianceConfirmed,
"payment_compliance_terms_version": operation_setting.CurrentComplianceTermsVersion,
"enable_redemption": true,
"payment_compliance_confirmed": true,
"waffo_pay_methods": func() interface{} {
if enableWaffo {
return setting.GetWaffoPayMethods()
-10
View File
@@ -17,7 +17,6 @@ import (
"github.com/QuantumNous/new-api/model"
"github.com/QuantumNous/new-api/service"
"github.com/QuantumNous/new-api/setting"
"github.com/QuantumNous/new-api/setting/operation_setting"
"github.com/QuantumNous/new-api/constant"
@@ -344,10 +343,6 @@ type TransferAffQuotaRequest struct {
}
func TransferAffQuota(c *gin.Context) {
if !requirePaymentCompliance(c) {
return
}
id := c.GetInt("id")
user, err := model.GetUserById(id, true)
if err != nil {
@@ -1104,11 +1099,6 @@ func getTopUpLock(userID int) *topUpTryLock {
}
func TopUp(c *gin.Context) {
if !operation_setting.IsPaymentComplianceConfirmed() {
common.ApiErrorI18n(c, i18n.MsgPaymentComplianceRequired)
return
}
id := c.GetInt("id")
lock := getTopUpLock(id)
if !lock.TryLock() {
+1 -1
View File
@@ -16,7 +16,7 @@ version: '3.4' # For compatibility with older Docker versions
services:
new-api:
image: calciumion/new-api:latest
image: git.viaeon.com/admin/new-api:latest
container_name: new-api
restart: always
command: --log-dir /app/logs
+2 -2
View File
@@ -18,10 +18,10 @@
"openai",
"claude"
],
"author": "QuantumNous",
"author": "modelstoken",
"repository": {
"type": "git",
"url": "https://github.com/QuantumNous/new-api"
"url": "https://git.viaeon.com/admin/new-api"
},
"devDependencies": {
"cross-env": "^7.0.3",
-1
View File
@@ -152,7 +152,6 @@ const (
MsgPaymentWebhookNotConfig = "payment.webhook_not_configured"
MsgPaymentPriceIdNotConfig = "payment.price_id_not_configured"
MsgPaymentCreemNotConfig = "payment.creem_not_configured"
MsgPaymentComplianceRequired = "payment.compliance_required"
)
// Topup related messages
+1 -1
View File
@@ -164,7 +164,7 @@ func main() {
common.SysLog(fmt.Sprintf("panic detected: %v", err))
c.JSON(http.StatusInternalServerError, gin.H{
"error": gin.H{
"message": fmt.Sprintf("Panic detected, error: %v. Please submit a issue here: https://github.com/Calcium-Ion/new-api", err),
"message": fmt.Sprintf("Panic detected, error: %v. Please submit a issue here: https://git.viaeon.com/admin/new-api/issues", err),
"type": "new_api_panic",
},
})
+4
View File
@@ -163,6 +163,10 @@ func TryUserAuth() func(c *gin.Context) {
if id != nil {
c.Set("id", id)
}
role := session.Get("role")
if role != nil {
c.Set("role", role)
}
c.Next()
}
}
+1 -1
View File
@@ -17,7 +17,7 @@ func RelayPanicRecover() gin.HandlerFunc {
common.SysLog(fmt.Sprintf("stacktrace from panic: %s", string(debug.Stack())))
c.JSON(http.StatusInternalServerError, gin.H{
"error": gin.H{
"message": fmt.Sprintf("Panic detected, error: %v. Please submit a issue here: https://github.com/Calcium-Ion/new-api", err),
"message": fmt.Sprintf("Panic detected, error: %v. Please submit a issue here: https://git.viaeon.com/admin/new-api/issues", err),
"type": "new_api_panic",
},
})
+96
View File
@@ -0,0 +1,96 @@
package model
import (
"time"
)
type Document struct {
Id int `json:"id" gorm:"primaryKey"`
Title string `json:"title" gorm:"not null"`
Slug string `json:"slug" gorm:"type:varchar(255);uniqueIndex;not null"`
Content string `json:"content" gorm:"type:text;not null"`
CategoryId *int `json:"category_id" gorm:"index"`
Visibility string `json:"visibility" gorm:"default:'public'"` // public, auth, admin
SortOrder int `json:"sort_order" gorm:"default:0"`
AuthorId int `json:"author_id" gorm:"not null"`
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
}
func GetDocuments(keyword string, visibility string, categoryId *int, startIdx int, num int) ([]*Document, int64, error) {
query := DB.Model(&Document{})
if keyword != "" {
like := "%" + keyword + "%"
query = query.Where("title LIKE ? OR content LIKE ?", like, like)
}
if visibility != "" {
query = query.Where("visibility = ?", visibility)
}
if categoryId != nil {
query = query.Where("category_id = ?", *categoryId)
}
var total int64
if err := query.Count(&total).Error; err != nil {
return nil, 0, err
}
var documents []*Document
if err := query.Order("sort_order ASC, id DESC").Offset(startIdx).Limit(num).Find(&documents).Error; err != nil {
return nil, 0, err
}
return documents, total, nil
}
func GetDocumentsByVisibility(keyword string, visibilities []string, categoryId *int, startIdx int, num int) ([]*Document, int64, error) {
query := DB.Model(&Document{})
if keyword != "" {
like := "%" + keyword + "%"
query = query.Where("title LIKE ? OR content LIKE ?", like, like)
}
if len(visibilities) > 0 {
query = query.Where("visibility IN ?", visibilities)
}
if categoryId != nil {
query = query.Where("category_id = ?", *categoryId)
}
var total int64
if err := query.Count(&total).Error; err != nil {
return nil, 0, err
}
var documents []*Document
if err := query.Order("sort_order ASC, id DESC").Offset(startIdx).Limit(num).Find(&documents).Error; err != nil {
return nil, 0, err
}
return documents, total, nil
}
func GetDocumentBySlug(slug string) (*Document, error) {
var doc Document
err := DB.Where("slug = ?", slug).First(&doc).Error
if err != nil {
return nil, err
}
return &doc, nil
}
func GetDocumentById(id int) (*Document, error) {
var doc Document
err := DB.First(&doc, id).Error
if err != nil {
return nil, err
}
return &doc, nil
}
func CreateDocument(doc *Document) error {
return DB.Create(doc).Error
}
func UpdateDocument(doc *Document) error {
return DB.Model(doc).Select("title", "slug", "content", "category_id", "visibility", "sort_order").Updates(doc).Error
}
func DeleteDocument(id int) error {
// Delete associated versions first
DB.Where("document_id = ?", id).Delete(&DocumentVersion{})
return DB.Delete(&Document{}, id).Error
}
+38
View File
@@ -0,0 +1,38 @@
package model
import (
"time"
)
type DocumentCategory struct {
Id int `json:"id" gorm:"primaryKey"`
Name string `json:"name" gorm:"not null"`
Slug string `json:"slug" gorm:"type:varchar(255);uniqueIndex;not null"`
ParentId *int `json:"parent_id" gorm:"index"`
SortOrder int `json:"sort_order" gorm:"default:0"`
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
}
func GetDocumentCategories() ([]*DocumentCategory, error) {
var categories []*DocumentCategory
err := DB.Order("sort_order ASC, id ASC").Find(&categories).Error
return categories, err
}
func GetDocumentCategoryTree() ([]*DocumentCategory, error) {
var categories []*DocumentCategory
err := DB.Order("sort_order ASC, id ASC").Find(&categories).Error
return categories, err
}
func CreateDocumentCategory(category *DocumentCategory) error {
return DB.Create(category).Error
}
func UpdateDocumentCategory(category *DocumentCategory) error {
return DB.Model(category).Select("name", "slug", "parent_id", "sort_order").Updates(category).Error
}
func DeleteDocumentCategory(id int) error {
return DB.Delete(&DocumentCategory{}, id).Error
}
+30
View File
@@ -0,0 +1,30 @@
package model
import (
"time"
)
type DocumentVersion struct {
Id int `json:"id" gorm:"primaryKey"`
DocumentId int `json:"document_id" gorm:"index;not null"`
Content string `json:"content" gorm:"type:text;not null"`
AuthorId int `json:"author_id" gorm:"not null"`
CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime"`
}
func GetDocumentVersions(documentId int, startIdx int, num int) ([]*DocumentVersion, int64, error) {
query := DB.Model(&DocumentVersion{}).Where("document_id = ?", documentId)
var total int64
if err := query.Count(&total).Error; err != nil {
return nil, 0, err
}
var versions []*DocumentVersion
if err := query.Order("id DESC").Offset(startIdx).Limit(num).Find(&versions).Error; err != nil {
return nil, 0, err
}
return versions, total, nil
}
func CreateDocumentVersion(version *DocumentVersion) error {
return DB.Create(version).Error
}
+6
View File
@@ -281,6 +281,9 @@ func migrateDB() error {
&CustomOAuthProvider{},
&UserOAuthBinding{},
&PerfMetric{},
&DocumentCategory{},
&Document{},
&DocumentVersion{},
)
if err != nil {
return err
@@ -330,6 +333,9 @@ func migrateDBFast() error {
{&CustomOAuthProvider{}, "CustomOAuthProvider"},
{&UserOAuthBinding{}, "UserOAuthBinding"},
{&PerfMetric{}, "PerfMetric"},
{&DocumentCategory{}, "DocumentCategory"},
{&Document{}, "Document"},
{&DocumentVersion{}, "DocumentVersion"},
}
// 动态计算migration数量,确保errChan缓冲区足够大
errChan := make(chan error, len(migrations))
+2 -3
View File
@@ -11,7 +11,6 @@ import (
"github.com/QuantumNous/new-api/common"
"github.com/QuantumNous/new-api/dto"
"github.com/QuantumNous/new-api/logger"
"github.com/QuantumNous/new-api/setting/operation_setting"
"github.com/bytedance/gopkg/util/gopool"
"gorm.io/gorm"
@@ -418,7 +417,7 @@ func (user *User) Insert(inviterId int) error {
if common.QuotaForNewUser > 0 {
RecordLog(user.Id, LogTypeSystem, fmt.Sprintf("新用户注册赠送 %s", logger.LogQuota(common.QuotaForNewUser)))
}
if inviterId != 0 && operation_setting.IsPaymentComplianceConfirmed() {
if inviterId != 0 {
if common.QuotaForInvitee > 0 {
_ = IncreaseUserQuota(user.Id, common.QuotaForInvitee, true)
RecordLog(user.Id, LogTypeSystem, fmt.Sprintf("使用邀请码赠送 %s", logger.LogQuota(common.QuotaForInvitee)))
@@ -479,7 +478,7 @@ func (user *User) FinalizeOAuthUserCreation(inviterId int) {
if common.QuotaForNewUser > 0 {
RecordLog(user.Id, LogTypeSystem, fmt.Sprintf("新用户注册赠送 %s", logger.LogQuota(common.QuotaForNewUser)))
}
if inviterId != 0 && operation_setting.IsPaymentComplianceConfirmed() {
if inviterId != 0 {
if common.QuotaForInvitee > 0 {
_ = IncreaseUserQuota(user.Id, common.QuotaForInvitee, true)
RecordLog(user.Id, LogTypeSystem, fmt.Sprintf("使用邀请码赠送 %s", logger.LogQuota(common.QuotaForInvitee)))
+15 -1
View File
@@ -164,6 +164,20 @@ func (a *Adaptor) GetRequestURL(info *relaycommon.RelayInfo) (string, error) {
url = strings.Replace(url, "{model}", info.UpstreamModelName, -1)
return url, nil
default:
// Handle coding plan special base URLs
if specialPlan, ok := constant.ChannelSpecialBases[info.ChannelBaseUrl]; ok && specialPlan.OpenAIBaseURL != "" {
if info.RelayFormat == types.RelayFormatClaude {
return fmt.Sprintf("%s/v1/messages", specialPlan.ClaudeBaseURL), nil
}
switch info.RelayMode {
case relayconstant.RelayModeEmbeddings:
return fmt.Sprintf("%s/embeddings", specialPlan.OpenAIBaseURL), nil
case relayconstant.RelayModeImagesGenerations:
return fmt.Sprintf("%s/images/generations", specialPlan.OpenAIBaseURL), nil
default:
return fmt.Sprintf("%s/chat/completions", specialPlan.OpenAIBaseURL), nil
}
}
if (info.RelayFormat == types.RelayFormatClaude || info.RelayFormat == types.RelayFormatGemini) &&
info.RelayMode != relayconstant.RelayModeResponses &&
info.RelayMode != relayconstant.RelayModeResponsesCompact {
@@ -221,7 +235,7 @@ func (a *Adaptor) SetupRequestHeader(c *gin.Context, header *http.Header, info *
header.Set("HTTP-Referer", "https://www.newapi.ai")
}
if header.Get("X-OpenRouter-Title") == "" {
header.Set("X-OpenRouter-Title", "New API")
header.Set("X-OpenRouter-Title", "ModelsToken")
}
}
return nil
+24 -1
View File
@@ -186,7 +186,6 @@ func SetApiRouter(router *gin.Engine) {
{
optionRoute.GET("/", controller.GetOptions)
optionRoute.PUT("/", controller.UpdateOption)
optionRoute.POST("/payment_compliance", controller.ConfirmPaymentCompliance)
optionRoute.GET("/channel_affinity_cache", controller.GetChannelAffinityCacheStats)
optionRoute.DELETE("/channel_affinity_cache", controller.ClearChannelAffinityCache)
optionRoute.POST("/rest_model_ratio", controller.ResetModelRatio)
@@ -347,6 +346,30 @@ func SetApiRouter(router *gin.Engine) {
taskRoute.GET("/", middleware.AdminAuth(), controller.GetAllTask)
}
// Document routes (public)
docsPublic := apiRouter.Group("/docs")
docsPublic.Use(middleware.TryUserAuth())
{
docsPublic.GET("/categories", controller.GetCategories)
docsPublic.GET("/", controller.GetDocuments)
docsPublic.GET("/:slug", controller.GetDocument)
}
// Document routes (admin) - use /admin/docs to avoid conflict with /:slug
docsAdmin := apiRouter.Group("/admin/docs")
docsAdmin.Use(middleware.AdminAuth())
{
docsAdmin.GET("/categories", controller.GetCategories)
docsAdmin.POST("/categories", controller.CreateCategory)
docsAdmin.PUT("/categories/:id", controller.UpdateCategory)
docsAdmin.DELETE("/categories/:id", controller.DeleteCategory)
docsAdmin.GET("/", controller.GetDocuments)
docsAdmin.POST("/", controller.CreateDocument)
docsAdmin.PUT("/:id", controller.UpdateDocument)
docsAdmin.DELETE("/:id", controller.DeleteDocument)
docsAdmin.GET("/:id/versions", controller.GetDocumentVersions)
}
vendorRoute := apiRouter.Group("/vendors")
vendorRoute.Use(middleware.AdminAuth())
{
+4 -3
View File
@@ -13,7 +13,7 @@ import (
"github.com/gin-gonic/gin"
)
// ThemeAssets holds the embedded frontend assets for both themes.
// ThemeAssets holds the embedded frontend assets for all themes.
type ThemeAssets struct {
DefaultBuildFS embed.FS
DefaultIndexPage []byte
@@ -37,9 +37,10 @@ func SetWebRouter(router *gin.Engine, assets ThemeAssets) {
return
}
c.Header("Cache-Control", "no-cache")
if common.GetTheme() == "classic" {
switch common.GetTheme() {
case "classic":
c.Data(http.StatusOK, "text/html; charset=utf-8", assets.ClassicIndexPage)
} else {
default:
c.Data(http.StatusOK, "text/html; charset=utf-8", assets.DefaultIndexPage)
}
})
@@ -5,16 +5,8 @@ import "github.com/QuantumNous/new-api/setting/config"
type PaymentSetting struct {
AmountOptions []int `json:"amount_options"`
AmountDiscount map[int]float64 `json:"amount_discount"` // 充值金额对应的折扣,例如 100 元 0.9 表示 100 元充值享受 9 折优惠
ComplianceConfirmed bool `json:"compliance_confirmed"`
ComplianceTermsVersion string `json:"compliance_terms_version"`
ComplianceConfirmedAt int64 `json:"compliance_confirmed_at"`
ComplianceConfirmedBy int `json:"compliance_confirmed_by"`
ComplianceConfirmedIP string `json:"compliance_confirmed_ip"`
}
const CurrentComplianceTermsVersion = "v1"
// 默认配置
var paymentSetting = PaymentSetting{
AmountOptions: []int{10, 20, 50, 100, 200, 500},
@@ -29,8 +21,3 @@ func init() {
func GetPaymentSetting() *PaymentSetting {
return &paymentSetting
}
func IsPaymentComplianceConfirmed() bool {
return paymentSetting.ComplianceConfirmed &&
paymentSetting.ComplianceTermsVersion == CurrentComplianceTermsVersion
}
+1 -1
View File
@@ -16,7 +16,7 @@
content="A unified AI model hub for aggregation & distribution. It supports cross-converting various LLMs into OpenAI-compatible, Claude-compatible, or Gemini-compatible formats. A centralized gateway for personal and enterprise model management."
/>
<meta name="generator" content="new-api" />
<title>New API</title>
<title>ModelsToken</title>
<!--umami-->
<!--Google Analytics-->
</head>
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 52 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 1.9 MiB

+2 -2
View File
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import React, { lazy, Suspense, useContext, useMemo } from 'react';
+2 -2
View File
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import React, { useContext, useEffect, useMemo, useRef, useState } from 'react';
+2 -2
View File
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import React, { useContext, useEffect, useRef } from 'react';
+2 -2
View File
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import React, { useEffect, useState } from 'react';
+2 -2
View File
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import React, { useEffect, useState } from 'react';
+2 -2
View File
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import React, { useContext, useEffect, useMemo, useRef, useState } from 'react';
+2 -2
View File
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import { API, showError, showSuccess } from '../../helpers';
import {
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import React, { useEffect, useMemo, useState } from 'react';
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import React, { useState } from 'react';
+2 -2
View File
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import React from 'react';
+2 -2
View File
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import React from 'react';
+2 -2
View File
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import React from 'react';
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import ReactMarkdown from 'react-markdown';
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import React, { useCallback, useEffect, useMemo, useState } from 'react';
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import React, { useEffect, useState } from 'react';
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import React from 'react';
+2 -2
View File
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import React, { useState } from 'react';
+2 -2
View File
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import React, { useState, useEffect, useRef } from 'react';
+2 -2
View File
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import React from 'react';
+2 -2
View File
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import React from 'react';
+2 -2
View File
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import React, { useState, useEffect, useCallback, useMemo } from 'react';
+2 -2
View File
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import React from 'react';
+2 -2
View File
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import React from 'react';
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import React, {
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import React, { useState, useRef, useEffect } from 'react';
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import React from 'react';
+2 -2
View File
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import React from 'react';
+2 -2
View File
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import React from 'react';
+2 -2
View File
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import React from 'react';
+2 -2
View File
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import React from 'react';
+2 -2
View File
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import React from 'react';
+2 -2
View File
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import React from 'react';
+2 -2
View File
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import React, { useContext, useEffect } from 'react';
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import React, { useRef } from 'react';
+7 -114
View File
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import React, { useEffect, useState, useMemo, useContext } from 'react';
@@ -56,14 +56,14 @@ const FooterBar = () => {
/>
</div>
<div className='grid grid-cols-1 sm:grid-cols-2 md:grid-cols-4 gap-8 w-full'>
<div className='grid grid-cols-1 sm:grid-cols-2 md:grid-cols-2 gap-8 w-full'>
<div className='text-left'>
<p className='!text-semi-color-text-0 font-semibold mb-5'>
{t('关于我们')}
</p>
<div className='flex flex-col gap-4'>
<a
href='https://docs.newapi.pro/wiki/project-introduction/'
href='https://modelstoken.com'
target='_blank'
rel='noopener noreferrer'
className='!text-semi-color-text-1'
@@ -71,21 +71,13 @@ const FooterBar = () => {
{t('关于项目')}
</a>
<a
href='https://docs.newapi.pro/support/community-interaction/'
href='https://modelstoken.com'
target='_blank'
rel='noopener noreferrer'
className='!text-semi-color-text-1'
>
{t('联系我们')}
</a>
<a
href='https://docs.newapi.pro/wiki/features-introduction/'
target='_blank'
rel='noopener noreferrer'
className='!text-semi-color-text-1'
>
{t('功能特性')}
</a>
</div>
</div>
@@ -95,7 +87,7 @@ const FooterBar = () => {
</p>
<div className='flex flex-col gap-4'>
<a
href='https://docs.newapi.pro/getting-started/'
href='https://modelstoken.com'
target='_blank'
rel='noopener noreferrer'
className='!text-semi-color-text-1'
@@ -103,15 +95,7 @@ const FooterBar = () => {
{t('快速开始')}
</a>
<a
href='https://docs.newapi.pro/installation/'
target='_blank'
rel='noopener noreferrer'
className='!text-semi-color-text-1'
>
{t('安装指南')}
</a>
<a
href='https://docs.newapi.pro/api/'
href='https://modelstoken.com'
target='_blank'
rel='noopener noreferrer'
className='!text-semi-color-text-1'
@@ -120,70 +104,6 @@ const FooterBar = () => {
</a>
</div>
</div>
<div className='text-left'>
<p className='!text-semi-color-text-0 font-semibold mb-5'>
{t('相关项目')}
</p>
<div className='flex flex-col gap-4'>
<a
href='https://github.com/songquanpeng/one-api'
target='_blank'
rel='noopener noreferrer'
className='!text-semi-color-text-1'
>
One API
</a>
<a
href='https://github.com/novicezk/midjourney-proxy'
target='_blank'
rel='noopener noreferrer'
className='!text-semi-color-text-1'
>
Midjourney-Proxy
</a>
<a
href='https://github.com/Calcium-Ion/new-api-key-tool'
target='_blank'
rel='noopener noreferrer'
className='!text-semi-color-text-1'
>
new-api-key-tool
</a>
</div>
</div>
<div className='text-left'>
<p className='!text-semi-color-text-0 font-semibold mb-5'>
{t('友情链接')}
</p>
<div className='flex flex-col gap-4'>
<a
href='https://github.com/Calcium-Ion/new-api-horizon'
target='_blank'
rel='noopener noreferrer'
className='!text-semi-color-text-1'
>
new-api-horizon
</a>
<a
href='https://github.com/coaidev/coai'
target='_blank'
rel='noopener noreferrer'
className='!text-semi-color-text-1'
>
CoAI
</a>
<a
href='https://www.gpt-load.com/'
target='_blank'
rel='noopener noreferrer'
className='!text-semi-color-text-1'
>
GPT-Load
</a>
</div>
</div>
</div>
</div>
)}
@@ -194,20 +114,6 @@ const FooterBar = () => {
© {currentYear} {systemName}. {t('版权所有')}
</Typography.Text>
</div>
<div className='text-sm'>
<span className='!text-semi-color-text-1'>
{t('设计与开发由')}{' '}
</span>
<a
href='https://github.com/QuantumNous/new-api'
target='_blank'
rel='noopener noreferrer'
className='!text-semi-color-primary font-medium'
>
New API
</a>
</div>
</div>
</footer>
),
@@ -227,19 +133,6 @@ const FooterBar = () => {
className='custom-footer na-cb6feafeb3990c78 text-sm !text-semi-color-text-1'
dangerouslySetInnerHTML={{ __html: footer }}
></div>
<div className='text-sm flex-shrink-0'>
<span className='!text-semi-color-text-1'>
{t('设计与开发由')}{' '}
</span>
<a
href='https://github.com/QuantumNous/new-api'
target='_blank'
rel='noopener noreferrer'
className='!text-semi-color-primary font-medium'
>
New API
</a>
</div>
</div>
</footer>
) : (
+2 -2
View File
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import React, { useEffect, useState, useContext, useMemo } from 'react';
+2 -2
View File
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import HeaderBar from './headerbar';
+2 -2
View File
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import React, { useContext, useEffect } from 'react';
+2 -2
View File
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import React, { useEffect, useMemo, useState } from 'react';
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import React from 'react';
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import React from 'react';
+2 -2
View File
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import React from 'react';
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import React from 'react';
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import React from 'react';
+2 -2
View File
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import React from 'react';
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import React from 'react';
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import React from 'react';
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import React, { useMemo } from 'react';
+2 -2
View File
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import React, { useRef } from 'react';
+2 -2
View File
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import React from 'react';
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import React from 'react';
+2 -2
View File
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import React from 'react';
+2 -2
View File
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import React, { useState, useMemo, useCallback } from 'react';
+2 -2
View File
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import React, { useRef } from 'react';
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import React, { useRef, useEffect, useCallback } from 'react';
@@ -1,5 +1,5 @@
/*
Copyright (C) 2025 QuantumNous
Copyright (C) 2025 modelstoken
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as
@@ -14,7 +14,7 @@ GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
For commercial licensing, please contact admin@modelstoken.com
*/
import React, { useState, useEffect } from 'react';

Some files were not shown because too many files have changed in this diff Show More