liuzhao521 commited on
Commit
4674012
·
0 Parent(s):

Deploy New API v0.9.25+ (commit b47cf4ef) to HuggingFace Spaces

Browse files
This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. .dockerignore +9 -0
  2. .env.example +76 -0
  3. .gitattributes +3 -0
  4. .github/FUNDING.yml +12 -0
  5. .github/ISSUE_TEMPLATE/bug_report.md +26 -0
  6. .github/ISSUE_TEMPLATE/bug_report_en.md +26 -0
  7. .github/ISSUE_TEMPLATE/config.yml +5 -0
  8. .github/ISSUE_TEMPLATE/feature_request.md +21 -0
  9. .github/ISSUE_TEMPLATE/feature_request_en.md +22 -0
  10. .github/PULL_REQUEST_TEMPLATE/pull_request_template.md +15 -0
  11. .github/workflows/docker-image-alpha.yml +151 -0
  12. .github/workflows/docker-image-arm64.yml +138 -0
  13. .github/workflows/electron-build.yml +141 -0
  14. .github/workflows/release.yml +142 -0
  15. .github/workflows/sync-to-gitee.yml +91 -0
  16. .gitignore +23 -0
  17. Dockerfile +44 -0
  18. Dockerfile.hf +44 -0
  19. LICENSE +103 -0
  20. README.en.md +447 -0
  21. README.fr.md +441 -0
  22. README.ja.md +450 -0
  23. README.md +459 -0
  24. VERSION +0 -0
  25. bin/migration_v0.2-v0.3.sql +6 -0
  26. bin/migration_v0.3-v0.4.sql +17 -0
  27. bin/time_test.sh +40 -0
  28. common/api_type.go +81 -0
  29. common/audio.go +296 -0
  30. common/constants.go +204 -0
  31. common/copy.go +19 -0
  32. common/crypto.go +32 -0
  33. common/custom-event.go +87 -0
  34. common/database.go +15 -0
  35. common/email-outlook-auth.go +40 -0
  36. common/email.go +93 -0
  37. common/embed-file-system.go +43 -0
  38. common/endpoint_defaults.go +33 -0
  39. common/endpoint_type.go +43 -0
  40. common/env.go +38 -0
  41. common/gin.go +234 -0
  42. common/go-channel.go +53 -0
  43. common/gopool.go +25 -0
  44. common/hash.go +34 -0
  45. common/init.go +144 -0
  46. common/ip.go +22 -0
  47. common/json.go +45 -0
  48. common/limiter/limiter.go +90 -0
  49. common/limiter/lua/rate_limit.lua +44 -0
  50. common/model.go +42 -0
.dockerignore ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ .github
2
+ .git
3
+ *.md
4
+ .vscode
5
+ .gitignore
6
+ Makefile
7
+ docs
8
+ .eslintcache
9
+ .gocache
.env.example ADDED
@@ -0,0 +1,76 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # 端口号
2
+ # PORT=3000
3
+ # 前端基础URL
4
+ # FRONTEND_BASE_URL=https://your-frontend-url.com
5
+
6
+
7
+ # 调试相关配置
8
+ # 启用pprof
9
+ # ENABLE_PPROF=true
10
+ # 启用调试模式
11
+ # DEBUG=true
12
+
13
+ # 数据库相关配置
14
+ # 数据库连接字符串
15
+ # SQL_DSN=user:password@tcp(127.0.0.1:3306)/dbname?parseTime=true
16
+ # 日志数据库连接字符串
17
+ # LOG_SQL_DSN=user:password@tcp(127.0.0.1:3306)/logdb?parseTime=true
18
+ # SQLite数据库路径
19
+ # SQLITE_PATH=/path/to/sqlite.db
20
+ # 数据库最大空闲连接数
21
+ # SQL_MAX_IDLE_CONNS=100
22
+ # 数据库最大打开连接数
23
+ # SQL_MAX_OPEN_CONNS=1000
24
+ # 数据库连接最大生命周期(秒)
25
+ # SQL_MAX_LIFETIME=60
26
+
27
+
28
+ # 缓存相关配置
29
+ # Redis连接字符串
30
+ # REDIS_CONN_STRING=redis://user:password@localhost:6379/0
31
+ # 同步频率(单位:秒)
32
+ # SYNC_FREQUENCY=60
33
+ # 内存缓存启用
34
+ # MEMORY_CACHE_ENABLED=true
35
+ # 渠道更新频率(单位:秒)
36
+ # CHANNEL_UPDATE_FREQUENCY=30
37
+ # 批量更新启用
38
+ # BATCH_UPDATE_ENABLED=true
39
+ # 批量更新间隔(单位:秒)
40
+ # BATCH_UPDATE_INTERVAL=5
41
+
42
+ # 任务和功能配置
43
+ # 更新任务启用
44
+ # UPDATE_TASK=true
45
+
46
+ # 对话超时设置
47
+ # 所有请求超时时间,单位秒,默认为0,表示不限制
48
+ # RELAY_TIMEOUT=0
49
+ # 流模式无响应超时时间,单位秒,如果出现空补全可以尝试改为更大值
50
+ # STREAMING_TIMEOUT=300
51
+
52
+ # Gemini 识别图片 最大图片数量
53
+ # GEMINI_VISION_MAX_IMAGE_NUM=16
54
+
55
+ # 会话密钥
56
+ # SESSION_SECRET=random_string
57
+
58
+ # 其他配置
59
+ # 生成默认token
60
+ # GENERATE_DEFAULT_TOKEN=false
61
+ # Cohere 安全设置
62
+ # COHERE_SAFETY_SETTING=NONE
63
+ # 是否统计图片token
64
+ # GET_MEDIA_TOKEN=true
65
+ # 是否在非流(stream=false)情况下统计图片token
66
+ # GET_MEDIA_TOKEN_NOT_STREAM=false
67
+ # 设置 Dify 渠道是否输出工作流和节点信息到客户端
68
+ # DIFY_DEBUG=true
69
+
70
+ # LinuxDo相关配置
71
+ LINUX_DO_TOKEN_ENDPOINT=https://connect.linux.do/oauth2/token
72
+ LINUX_DO_USER_ENDPOINT=https://connect.linux.do/api/user
73
+
74
+ # 节点类型
75
+ # 如果是主节点则为master
76
+ # NODE_TYPE=master
.gitattributes ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ *.lockb filter=lfs diff=lfs merge=lfs -text
2
+ *.png filter=lfs diff=lfs merge=lfs -text
3
+ *.ico filter=lfs diff=lfs merge=lfs -text
.github/FUNDING.yml ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # These are supported funding model platforms
2
+
3
+ github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4
+ patreon: # Replace with a single Patreon username
5
+ open_collective: # Replace with a single Open Collective username
6
+ ko_fi: # Replace with a single Ko-fi username
7
+ tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8
+ community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9
+ liberapay: # Replace with a single Liberapay username
10
+ issuehunt: # Replace with a single IssueHunt username
11
+ otechie: # Replace with a single Otechie username
12
+ custom: ['https://afdian.com/a/new-api'] # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
.github/ISSUE_TEMPLATE/bug_report.md ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ name: 报告问题
3
+ about: 使用简练详细的语言描述你遇到的问题
4
+ title: ''
5
+ labels: bug
6
+ assignees: ''
7
+
8
+ ---
9
+
10
+ **例行检查**
11
+
12
+ [//]: # (方框内删除已有的空格,填 x 号)
13
+ + [ ] 我已确认目前没有类似 issue
14
+ + [ ] 我已确认我已升级到最新版本
15
+ + [ ] 我已完整查看过项目 README,尤其是常见问题部分
16
+ + [ ] 我理解并愿意跟进此 issue,协助测试和提供反馈
17
+ + [ ] 我理解并认可上述内容,并理解项目维护者精力有限,**不遵循规则的 issue 可能会被无视或直接关闭**
18
+
19
+ **问题描述**
20
+
21
+ **复现步骤**
22
+
23
+ **预期结果**
24
+
25
+ **相关截图**
26
+ 如果没有的话,请删除此节。
.github/ISSUE_TEMPLATE/bug_report_en.md ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ name: Bug Report
3
+ about: Describe the issue you encountered with clear and detailed language
4
+ title: ''
5
+ labels: bug
6
+ assignees: ''
7
+
8
+ ---
9
+
10
+ **Routine Checks**
11
+
12
+ [//]: # (Remove the space in the box and fill with an x)
13
+ + [ ] I have confirmed there are no similar issues currently
14
+ + [ ] I have confirmed I have upgraded to the latest version
15
+ + [ ] I have thoroughly read the project README, especially the FAQ section
16
+ + [ ] I understand and am willing to follow up on this issue, assist with testing and provide feedback
17
+ + [ ] I understand and acknowledge the above, and understand that project maintainers have limited time and energy, **issues that do not follow the rules may be ignored or closed directly**
18
+
19
+ **Issue Description**
20
+
21
+ **Steps to Reproduce**
22
+
23
+ **Expected Result**
24
+
25
+ **Related Screenshots**
26
+ If none, please delete this section.
.github/ISSUE_TEMPLATE/config.yml ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ blank_issues_enabled: false
2
+ contact_links:
3
+ - name: 项目群聊
4
+ url: https://private-user-images.githubusercontent.com/61247483/283011625-de536a8a-0161-47a7-a0a2-66ef6de81266.jpeg?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTEiLCJleHAiOjE3MDIyMjQzOTAsIm5iZiI6MTcwMjIyNDA5MCwicGF0aCI6Ii82MTI0NzQ4My8yODMwMTE2MjUtZGU1MzZhOGEtMDE2MS00N2E3LWEwYTItNjZlZjZkZTgxMjY2LmpwZWc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBSVdOSllBWDRDU1ZFSDUzQSUyRjIwMjMxMjEwJTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDIzMTIxMFQxNjAxMzBaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT02MGIxYmM3ZDQyYzBkOTA2ZTYyYmVmMzQ1NjY4NjM1YjY0NTUzNTM5NjE1NDZkYTIzODdhYTk4ZjZjODJmYzY2JlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCZhY3Rvcl9pZD0wJmtleV9pZD0wJnJlcG9faWQ9MCJ9.TJ8CTfOSwR0-CHS1KLfomqgL0e4YH1luy8lSLrkv5Zg
5
+ about: QQ 群:629454374
.github/ISSUE_TEMPLATE/feature_request.md ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ name: 功能请求
3
+ about: 使用简练详细的语言描述希望加入的新功能
4
+ title: ''
5
+ labels: enhancement
6
+ assignees: ''
7
+
8
+ ---
9
+
10
+ **例行检查**
11
+
12
+ [//]: # (方框内删除已有的空格,填 x 号)
13
+ + [ ] 我已确认目前没有类似 issue
14
+ + [ ] 我已确认我已升级到最新版本
15
+ + [ ] 我已完整查看过项目 README,已确定现有版本无法满足需求
16
+ + [ ] 我理解并愿意跟进此 issue,协助测试和提供反馈
17
+ + [ ] 我理解并认可上述内容,并理解项目维护者精力有限,**不遵循规则的 issue 可能会被无视或直接关闭**
18
+
19
+ **功能描述**
20
+
21
+ **应用场景**
.github/ISSUE_TEMPLATE/feature_request_en.md ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ name: Feature Request
3
+ about: Describe the new feature you would like to add with clear and detailed language
4
+ title: ''
5
+ labels: enhancement
6
+ assignees: ''
7
+
8
+ ---
9
+
10
+ **Routine Checks**
11
+
12
+ [//]: # (Remove the space in the box and fill with an x)
13
+ + [ ] I have confirmed there are no similar issues currently
14
+ + [ ] I have confirmed I have upgraded to the latest version
15
+ + [ ] I have thoroughly read the project README and confirmed the current version cannot meet my needs
16
+ + [ ] I understand and am willing to follow up on this issue, assist with testing and provide feedback
17
+ + [ ] I understand and acknowledge the above, and understand that project maintainers have limited time and energy, **issues that do not follow the rules may be ignored or closed directly**
18
+
19
+ **Feature Description**
20
+
21
+ **Use Case**
22
+
.github/PULL_REQUEST_TEMPLATE/pull_request_template.md ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ### PR 类型
2
+
3
+ - [ ] Bug 修复
4
+ - [ ] 新功能
5
+ - [ ] 文档更新
6
+ - [ ] 其他
7
+
8
+ ### PR 是否包含破坏性更新?
9
+
10
+ - [ ] 是
11
+ - [ ] 否
12
+
13
+ ### PR 描述
14
+
15
+ **请在下方详细描述您的 PR,包括目的、实现细节等。**
.github/workflows/docker-image-alpha.yml ADDED
@@ -0,0 +1,151 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Publish Docker image (alpha)
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - alpha
7
+ workflow_dispatch:
8
+ inputs:
9
+ name:
10
+ description: "reason"
11
+ required: false
12
+
13
+ jobs:
14
+ build_single_arch:
15
+ name: Build & push (${{ matrix.arch }}) [native]
16
+ strategy:
17
+ fail-fast: false
18
+ matrix:
19
+ include:
20
+ - arch: amd64
21
+ platform: linux/amd64
22
+ runner: ubuntu-latest
23
+ - arch: arm64
24
+ platform: linux/arm64
25
+ runner: ubuntu-24.04-arm
26
+ runs-on: ${{ matrix.runner }}
27
+ permissions:
28
+ packages: write
29
+ contents: read
30
+ steps:
31
+ - name: Check out (shallow)
32
+ uses: actions/checkout@v4
33
+ with:
34
+ fetch-depth: 1
35
+
36
+ - name: Determine alpha version
37
+ id: version
38
+ run: |
39
+ VERSION="alpha-$(date +'%Y%m%d')-$(git rev-parse --short HEAD)"
40
+ echo "$VERSION" > VERSION
41
+ echo "value=$VERSION" >> $GITHUB_OUTPUT
42
+ echo "VERSION=$VERSION" >> $GITHUB_ENV
43
+ echo "Publishing version: $VERSION for ${{ matrix.arch }}"
44
+
45
+ - name: Normalize GHCR repository
46
+ run: echo "GHCR_REPOSITORY=${GITHUB_REPOSITORY,,}" >> $GITHUB_ENV
47
+
48
+ - name: Set up Docker Buildx
49
+ uses: docker/setup-buildx-action@v3
50
+
51
+ - name: Log in to Docker Hub
52
+ uses: docker/login-action@v3
53
+ with:
54
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
55
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
56
+
57
+ - name: Log in to GHCR
58
+ uses: docker/login-action@v3
59
+ with:
60
+ registry: ghcr.io
61
+ username: ${{ github.actor }}
62
+ password: ${{ secrets.GITHUB_TOKEN }}
63
+
64
+ - name: Extract metadata (labels)
65
+ id: meta
66
+ uses: docker/metadata-action@v5
67
+ with:
68
+ images: |
69
+ calciumion/new-api
70
+ ghcr.io/${{ env.GHCR_REPOSITORY }}
71
+
72
+ - name: Build & push single-arch (to both registries)
73
+ uses: docker/build-push-action@v6
74
+ with:
75
+ context: .
76
+ platforms: ${{ matrix.platform }}
77
+ push: true
78
+ tags: |
79
+ calciumion/new-api:alpha-${{ matrix.arch }}
80
+ calciumion/new-api:${{ steps.version.outputs.value }}-${{ matrix.arch }}
81
+ ghcr.io/${{ env.GHCR_REPOSITORY }}:alpha-${{ matrix.arch }}
82
+ ghcr.io/${{ env.GHCR_REPOSITORY }}:${{ steps.version.outputs.value }}-${{ matrix.arch }}
83
+ labels: ${{ steps.meta.outputs.labels }}
84
+ cache-from: type=gha
85
+ cache-to: type=gha,mode=max
86
+ provenance: false
87
+ sbom: false
88
+
89
+ create_manifests:
90
+ name: Create multi-arch manifests (Docker Hub + GHCR)
91
+ needs: [build_single_arch]
92
+ runs-on: ubuntu-latest
93
+ permissions:
94
+ packages: write
95
+ contents: read
96
+ steps:
97
+ - name: Check out (shallow)
98
+ uses: actions/checkout@v4
99
+ with:
100
+ fetch-depth: 1
101
+
102
+ - name: Normalize GHCR repository
103
+ run: echo "GHCR_REPOSITORY=${GITHUB_REPOSITORY,,}" >> $GITHUB_ENV
104
+
105
+ - name: Determine alpha version
106
+ id: version
107
+ run: |
108
+ VERSION="alpha-$(date +'%Y%m%d')-$(git rev-parse --short HEAD)"
109
+ echo "value=$VERSION" >> $GITHUB_OUTPUT
110
+ echo "VERSION=$VERSION" >> $GITHUB_ENV
111
+
112
+ - name: Log in to Docker Hub
113
+ uses: docker/login-action@v3
114
+ with:
115
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
116
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
117
+
118
+ - name: Create & push manifest (Docker Hub - alpha)
119
+ run: |
120
+ docker buildx imagetools create \
121
+ -t calciumion/new-api:alpha \
122
+ calciumion/new-api:alpha-amd64 \
123
+ calciumion/new-api:alpha-arm64
124
+
125
+ - name: Create & push manifest (Docker Hub - versioned alpha)
126
+ run: |
127
+ docker buildx imagetools create \
128
+ -t calciumion/new-api:${VERSION} \
129
+ calciumion/new-api:${VERSION}-amd64 \
130
+ calciumion/new-api:${VERSION}-arm64
131
+
132
+ - name: Log in to GHCR
133
+ uses: docker/login-action@v3
134
+ with:
135
+ registry: ghcr.io
136
+ username: ${{ github.actor }}
137
+ password: ${{ secrets.GITHUB_TOKEN }}
138
+
139
+ - name: Create & push manifest (GHCR - alpha)
140
+ run: |
141
+ docker buildx imagetools create \
142
+ -t ghcr.io/${GHCR_REPOSITORY}:alpha \
143
+ ghcr.io/${GHCR_REPOSITORY}:alpha-amd64 \
144
+ ghcr.io/${GHCR_REPOSITORY}:alpha-arm64
145
+
146
+ - name: Create & push manifest (GHCR - versioned alpha)
147
+ run: |
148
+ docker buildx imagetools create \
149
+ -t ghcr.io/${GHCR_REPOSITORY}:${VERSION} \
150
+ ghcr.io/${GHCR_REPOSITORY}:${VERSION}-amd64 \
151
+ ghcr.io/${GHCR_REPOSITORY}:${VERSION}-arm64
.github/workflows/docker-image-arm64.yml ADDED
@@ -0,0 +1,138 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Publish Docker image (Multi Registries, native amd64+arm64)
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - '*'
7
+
8
+ jobs:
9
+ build_single_arch:
10
+ name: Build & push (${{ matrix.arch }}) [native]
11
+ strategy:
12
+ fail-fast: false
13
+ matrix:
14
+ include:
15
+ - arch: amd64
16
+ platform: linux/amd64
17
+ runner: ubuntu-latest
18
+ - arch: arm64
19
+ platform: linux/arm64
20
+ runner: ubuntu-24.04-arm
21
+ runs-on: ${{ matrix.runner }}
22
+
23
+ permissions:
24
+ packages: write
25
+ contents: read
26
+
27
+ steps:
28
+ - name: Check out (shallow)
29
+ uses: actions/checkout@v4
30
+ with:
31
+ fetch-depth: 1
32
+
33
+ - name: Resolve tag & write VERSION
34
+ run: |
35
+ git fetch --tags --force --depth=1
36
+ TAG=${GITHUB_REF#refs/tags/}
37
+ echo "TAG=$TAG" >> $GITHUB_ENV
38
+ echo "$TAG" > VERSION
39
+ echo "Building tag: $TAG for ${{ matrix.arch }}"
40
+
41
+
42
+ # - name: Normalize GHCR repository
43
+ # run: echo "GHCR_REPOSITORY=${GITHUB_REPOSITORY,,}" >> $GITHUB_ENV
44
+
45
+ - name: Set up Docker Buildx
46
+ uses: docker/setup-buildx-action@v3
47
+
48
+ - name: Log in to Docker Hub
49
+ uses: docker/login-action@v3
50
+ with:
51
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
52
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
53
+
54
+ # - name: Log in to GHCR
55
+ # uses: docker/login-action@v3
56
+ # with:
57
+ # registry: ghcr.io
58
+ # username: ${{ github.actor }}
59
+ # password: ${{ secrets.GITHUB_TOKEN }}
60
+
61
+ - name: Extract metadata (labels)
62
+ id: meta
63
+ uses: docker/metadata-action@v5
64
+ with:
65
+ images: |
66
+ calciumion/new-api
67
+ # ghcr.io/${{ env.GHCR_REPOSITORY }}
68
+
69
+ - name: Build & push single-arch (to both registries)
70
+ uses: docker/build-push-action@v6
71
+ with:
72
+ context: .
73
+ platforms: ${{ matrix.platform }}
74
+ push: true
75
+ tags: |
76
+ calciumion/new-api:${{ env.TAG }}-${{ matrix.arch }}
77
+ calciumion/new-api:latest-${{ matrix.arch }}
78
+ # ghcr.io/${{ env.GHCR_REPOSITORY }}:${{ env.TAG }}-${{ matrix.arch }}
79
+ # ghcr.io/${{ env.GHCR_REPOSITORY }}:latest-${{ matrix.arch }}
80
+ labels: ${{ steps.meta.outputs.labels }}
81
+ cache-from: type=gha
82
+ cache-to: type=gha,mode=max
83
+ provenance: false
84
+ sbom: false
85
+
86
+ create_manifests:
87
+ name: Create multi-arch manifests (Docker Hub)
88
+ needs: [build_single_arch]
89
+ runs-on: ubuntu-latest
90
+ if: startsWith(github.ref, 'refs/tags/')
91
+ steps:
92
+ - name: Extract tag
93
+ run: echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV
94
+ #
95
+ # - name: Normalize GHCR repository
96
+ # run: echo "GHCR_REPOSITORY=${GITHUB_REPOSITORY,,}" >> $GITHUB_ENV
97
+
98
+ - name: Log in to Docker Hub
99
+ uses: docker/login-action@v3
100
+ with:
101
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
102
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
103
+
104
+ - name: Create & push manifest (Docker Hub - version)
105
+ run: |
106
+ docker buildx imagetools create \
107
+ -t calciumion/new-api:${TAG} \
108
+ calciumion/new-api:${TAG}-amd64 \
109
+ calciumion/new-api:${TAG}-arm64
110
+
111
+ - name: Create & push manifest (Docker Hub - latest)
112
+ run: |
113
+ docker buildx imagetools create \
114
+ -t calciumion/new-api:latest \
115
+ calciumion/new-api:latest-amd64 \
116
+ calciumion/new-api:latest-arm64
117
+
118
+ # ---- GHCR ----
119
+ # - name: Log in to GHCR
120
+ # uses: docker/login-action@v3
121
+ # with:
122
+ # registry: ghcr.io
123
+ # username: ${{ github.actor }}
124
+ # password: ${{ secrets.GITHUB_TOKEN }}
125
+
126
+ # - name: Create & push manifest (GHCR - version)
127
+ # run: |
128
+ # docker buildx imagetools create \
129
+ # -t ghcr.io/${GHCR_REPOSITORY}:${TAG} \
130
+ # ghcr.io/${GHCR_REPOSITORY}:${TAG}-amd64 \
131
+ # ghcr.io/${GHCR_REPOSITORY}:${TAG}-arm64
132
+ #
133
+ # - name: Create & push manifest (GHCR - latest)
134
+ # run: |
135
+ # docker buildx imagetools create \
136
+ # -t ghcr.io/${GHCR_REPOSITORY}:latest \
137
+ # ghcr.io/${GHCR_REPOSITORY}:latest-amd64 \
138
+ # ghcr.io/${GHCR_REPOSITORY}:latest-arm64
.github/workflows/electron-build.yml ADDED
@@ -0,0 +1,141 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Build Electron App
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - '*' # Triggers on version tags like v1.0.0
7
+ - '!*-*' # Ignore pre-release tags like v1.0.0-beta
8
+ - '!*-alpha*' # Ignore alpha tags like v1.0.0-alpha
9
+ workflow_dispatch: # Allows manual triggering
10
+
11
+ jobs:
12
+ build:
13
+ strategy:
14
+ matrix:
15
+ # os: [macos-latest, windows-latest]
16
+ os: [windows-latest]
17
+
18
+ runs-on: ${{ matrix.os }}
19
+ defaults:
20
+ run:
21
+ shell: bash
22
+
23
+ steps:
24
+ - name: Checkout code
25
+ uses: actions/checkout@v4
26
+ with:
27
+ fetch-depth: 0
28
+
29
+ - name: Setup Bun
30
+ uses: oven-sh/setup-bun@v2
31
+ with:
32
+ bun-version: latest
33
+
34
+ - name: Setup Node.js
35
+ uses: actions/setup-node@v4
36
+ with:
37
+ node-version: '20'
38
+
39
+ - name: Setup Go
40
+ uses: actions/setup-go@v5
41
+ with:
42
+ go-version: '>=1.25.1'
43
+
44
+ - name: Build frontend
45
+ env:
46
+ CI: ""
47
+ NODE_OPTIONS: "--max-old-space-size=4096"
48
+ run: |
49
+ cd web
50
+ bun install
51
+ DISABLE_ESLINT_PLUGIN='true' VITE_REACT_APP_VERSION=$(git describe --tags) bun run build
52
+ cd ..
53
+
54
+ # - name: Build Go binary (macos/Linux)
55
+ # if: runner.os != 'Windows'
56
+ # run: |
57
+ # go mod download
58
+ # go build -ldflags "-s -w -X 'new-api/common.Version=$(git describe --tags)' -extldflags '-static'" -o new-api
59
+
60
+ - name: Build Go binary (Windows)
61
+ if: runner.os == 'Windows'
62
+ run: |
63
+ go mod download
64
+ go build -ldflags "-s -w -X 'new-api/common.Version=$(git describe --tags)'" -o new-api.exe
65
+
66
+ - name: Update Electron version
67
+ run: |
68
+ cd electron
69
+ VERSION=$(git describe --tags)
70
+ VERSION=${VERSION#v} # Remove 'v' prefix if present
71
+ # Convert to valid semver: take first 3 components and convert rest to prerelease format
72
+ # e.g., 0.9.3-patch.1 -> 0.9.3-patch.1
73
+ if [[ $VERSION =~ ^([0-9]+)\.([0-9]+)\.([0-9]+)(.*)$ ]]; then
74
+ MAJOR=${BASH_REMATCH[1]}
75
+ MINOR=${BASH_REMATCH[2]}
76
+ PATCH=${BASH_REMATCH[3]}
77
+ REST=${BASH_REMATCH[4]}
78
+
79
+ VERSION="$MAJOR.$MINOR.$PATCH"
80
+
81
+ # If there's extra content, append it without adding -dev
82
+ if [[ -n "$REST" ]]; then
83
+ VERSION="$VERSION$REST"
84
+ fi
85
+ fi
86
+ npm version $VERSION --no-git-tag-version --allow-same-version
87
+
88
+ - name: Install Electron dependencies
89
+ run: |
90
+ cd electron
91
+ npm install
92
+
93
+ # - name: Build Electron app (macOS)
94
+ # if: runner.os == 'macOS'
95
+ # run: |
96
+ # cd electron
97
+ # npm run build:mac
98
+ # env:
99
+ # CSC_IDENTITY_AUTO_DISCOVERY: false # Skip code signing
100
+
101
+ - name: Build Electron app (Windows)
102
+ if: runner.os == 'Windows'
103
+ run: |
104
+ cd electron
105
+ npm run build:win
106
+
107
+ # - name: Upload artifacts (macOS)
108
+ # if: runner.os == 'macOS'
109
+ # uses: actions/upload-artifact@v4
110
+ # with:
111
+ # name: macos-build
112
+ # path: |
113
+ # electron/dist/*.dmg
114
+ # electron/dist/*.zip
115
+
116
+ - name: Upload artifacts (Windows)
117
+ if: runner.os == 'Windows'
118
+ uses: actions/upload-artifact@v4
119
+ with:
120
+ name: windows-build
121
+ path: |
122
+ electron/dist/*.exe
123
+
124
+ release:
125
+ needs: build
126
+ runs-on: ubuntu-latest
127
+ if: startsWith(github.ref, 'refs/tags/')
128
+ permissions:
129
+ contents: write
130
+
131
+ steps:
132
+ - name: Download all artifacts
133
+ uses: actions/download-artifact@v4
134
+
135
+ - name: Upload to Release
136
+ uses: softprops/action-gh-release@v2
137
+ with:
138
+ files: |
139
+ windows-build/*
140
+ env:
141
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
.github/workflows/release.yml ADDED
@@ -0,0 +1,142 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Release (Linux, macOS, Windows)
2
+ permissions:
3
+ contents: write
4
+
5
+ on:
6
+ workflow_dispatch:
7
+ inputs:
8
+ name:
9
+ description: 'reason'
10
+ required: false
11
+ push:
12
+ tags:
13
+ - '*'
14
+ - '!*-alpha*'
15
+
16
+ jobs:
17
+ linux:
18
+ name: Linux Release
19
+ runs-on: ubuntu-latest
20
+ steps:
21
+ - name: Checkout
22
+ uses: actions/checkout@v3
23
+ with:
24
+ fetch-depth: 0
25
+ - name: Determine Version
26
+ run: |
27
+ VERSION=$(git describe --tags)
28
+ echo "VERSION=$VERSION" >> $GITHUB_ENV
29
+ - uses: oven-sh/setup-bun@v2
30
+ with:
31
+ bun-version: latest
32
+ - name: Build Frontend
33
+ env:
34
+ CI: ""
35
+ run: |
36
+ cd web
37
+ bun install
38
+ DISABLE_ESLINT_PLUGIN='true' VITE_REACT_APP_VERSION=$VERSION bun run build
39
+ cd ..
40
+ - name: Set up Go
41
+ uses: actions/setup-go@v3
42
+ with:
43
+ go-version: '>=1.25.1'
44
+ - name: Build Backend (amd64)
45
+ run: |
46
+ go mod download
47
+ go build -ldflags "-s -w -X 'new-api/common.Version=$VERSION' -extldflags '-static'" -o new-api-$VERSION
48
+ - name: Build Backend (arm64)
49
+ run: |
50
+ sudo apt-get update
51
+ DEBIAN_FRONTEND=noninteractive sudo apt-get install -y gcc-aarch64-linux-gnu
52
+ 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
53
+ - name: Release
54
+ uses: softprops/action-gh-release@v2
55
+ if: startsWith(github.ref, 'refs/tags/')
56
+ with:
57
+ files: |
58
+ new-api-*
59
+ env:
60
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
61
+
62
+ macos:
63
+ name: macOS Release
64
+ runs-on: macos-latest
65
+ steps:
66
+ - name: Checkout
67
+ uses: actions/checkout@v3
68
+ with:
69
+ fetch-depth: 0
70
+ - name: Determine Version
71
+ run: |
72
+ VERSION=$(git describe --tags)
73
+ echo "VERSION=$VERSION" >> $GITHUB_ENV
74
+ - uses: oven-sh/setup-bun@v2
75
+ with:
76
+ bun-version: latest
77
+ - name: Build Frontend
78
+ env:
79
+ CI: ""
80
+ NODE_OPTIONS: "--max-old-space-size=4096"
81
+ run: |
82
+ cd web
83
+ bun install
84
+ DISABLE_ESLINT_PLUGIN='true' VITE_REACT_APP_VERSION=$VERSION bun run build
85
+ cd ..
86
+ - name: Set up Go
87
+ uses: actions/setup-go@v3
88
+ with:
89
+ go-version: '>=1.25.1'
90
+ - name: Build Backend
91
+ run: |
92
+ go mod download
93
+ go build -ldflags "-X 'new-api/common.Version=$VERSION'" -o new-api-macos-$VERSION
94
+ - name: Release
95
+ uses: softprops/action-gh-release@v2
96
+ if: startsWith(github.ref, 'refs/tags/')
97
+ with:
98
+ files: new-api-macos-*
99
+ env:
100
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
101
+
102
+ windows:
103
+ name: Windows Release
104
+ runs-on: windows-latest
105
+ defaults:
106
+ run:
107
+ shell: bash
108
+ steps:
109
+ - name: Checkout
110
+ uses: actions/checkout@v3
111
+ with:
112
+ fetch-depth: 0
113
+ - name: Determine Version
114
+ run: |
115
+ VERSION=$(git describe --tags)
116
+ echo "VERSION=$VERSION" >> $GITHUB_ENV
117
+ - uses: oven-sh/setup-bun@v2
118
+ with:
119
+ bun-version: latest
120
+ - name: Build Frontend
121
+ env:
122
+ CI: ""
123
+ run: |
124
+ cd web
125
+ bun install
126
+ DISABLE_ESLINT_PLUGIN='true' VITE_REACT_APP_VERSION=$VERSION bun run build
127
+ cd ..
128
+ - name: Set up Go
129
+ uses: actions/setup-go@v3
130
+ with:
131
+ go-version: '>=1.25.1'
132
+ - name: Build Backend
133
+ run: |
134
+ go mod download
135
+ go build -ldflags "-s -w -X 'new-api/common.Version=$VERSION'" -o new-api-$VERSION.exe
136
+ - name: Release
137
+ uses: softprops/action-gh-release@v2
138
+ if: startsWith(github.ref, 'refs/tags/')
139
+ with:
140
+ files: new-api-*.exe
141
+ env:
142
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
.github/workflows/sync-to-gitee.yml ADDED
@@ -0,0 +1,91 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ name: Sync Release to Gitee
2
+
3
+ permissions:
4
+ contents: read
5
+
6
+ on:
7
+ workflow_dispatch:
8
+ inputs:
9
+ tag_name:
10
+ description: 'Release Tag to sync (e.g. v1.0.0)'
11
+ required: true
12
+ type: string
13
+
14
+ # 配置你的 Gitee 仓库信息
15
+ env:
16
+ GITEE_OWNER: 'QuantumNous' # 修改为你的 Gitee 用户名
17
+ GITEE_REPO: 'new-api' # 修改为你的 Gitee 仓库名
18
+
19
+ jobs:
20
+ sync-to-gitee:
21
+ runs-on: sync
22
+ steps:
23
+ - name: Checkout
24
+ uses: actions/checkout@v3
25
+ with:
26
+ fetch-depth: 0
27
+
28
+ - name: Get Release Info
29
+ id: release_info
30
+ env:
31
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
32
+ TAG_NAME: ${{ github.event.inputs.tag_name }}
33
+ run: |
34
+ # 获取 release 信息
35
+ RELEASE_INFO=$(gh release view "$TAG_NAME" --json name,body,tagName,targetCommitish)
36
+
37
+ RELEASE_NAME=$(echo "$RELEASE_INFO" | jq -r '.name')
38
+ TARGET_COMMITISH=$(echo "$RELEASE_INFO" | jq -r '.targetCommitish')
39
+
40
+ # 使用多行字符串输出
41
+ {
42
+ echo "release_name=$RELEASE_NAME"
43
+ echo "target_commitish=$TARGET_COMMITISH"
44
+ echo "release_body<<EOF"
45
+ echo "$RELEASE_INFO" | jq -r '.body'
46
+ echo "EOF"
47
+ } >> $GITHUB_OUTPUT
48
+
49
+ # 下载 release 的所有附件
50
+ gh release download "$TAG_NAME" --dir ./release_assets || echo "No assets to download"
51
+
52
+ # 列出下载的文件
53
+ ls -la ./release_assets/ || echo "No assets directory"
54
+
55
+ - name: Create Gitee Release
56
+ id: create_release
57
+ uses: nICEnnnnnnnLee/action-gitee-release@v2.0.0
58
+ with:
59
+ gitee_action: create_release
60
+ gitee_owner: ${{ env.GITEE_OWNER }}
61
+ gitee_repo: ${{ env.GITEE_REPO }}
62
+ gitee_token: ${{ secrets.GITEE_TOKEN }}
63
+ gitee_tag_name: ${{ github.event.inputs.tag_name }}
64
+ gitee_release_name: ${{ steps.release_info.outputs.release_name }}
65
+ gitee_release_body: ${{ steps.release_info.outputs.release_body }}
66
+ gitee_target_commitish: ${{ steps.release_info.outputs.target_commitish }}
67
+
68
+ - name: Upload Assets to Gitee
69
+ if: hashFiles('release_assets/*') != ''
70
+ uses: nICEnnnnnnnLee/action-gitee-release@v2.0.0
71
+ with:
72
+ gitee_action: upload_asset
73
+ gitee_owner: ${{ env.GITEE_OWNER }}
74
+ gitee_repo: ${{ env.GITEE_REPO }}
75
+ gitee_token: ${{ secrets.GITEE_TOKEN }}
76
+ gitee_release_id: ${{ steps.create_release.outputs.release-id }}
77
+ gitee_upload_retry_times: 3
78
+ gitee_files: |
79
+ release_assets/*
80
+
81
+ - name: Cleanup
82
+ if: always()
83
+ run: |
84
+ rm -rf release_assets/
85
+
86
+ - name: Summary
87
+ if: success()
88
+ run: |
89
+ echo "✅ Successfully synced release ${{ github.event.inputs.tag_name }} to Gitee!"
90
+ echo "🔗 Gitee Release URL: https://gitee.com/${{ env.GITEE_OWNER }}/${{ env.GITEE_REPO }}/releases/tag/${{ github.event.inputs.tag_name }}"
91
+
.gitignore ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ .idea
2
+ .vscode
3
+ .zed
4
+ upload
5
+ *.exe
6
+ *.db
7
+ build
8
+ *.db-journal
9
+ logs
10
+ web/dist
11
+ .env
12
+ one-api
13
+ new-api
14
+ /__debug_bin*
15
+ .DS_Store
16
+ tiktoken_cache
17
+ .eslintcache
18
+ .gocache
19
+ .cache
20
+ web/bun.lock
21
+
22
+ electron/node_modules
23
+ electron/dist
Dockerfile ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM oven/bun:latest AS builder
2
+
3
+ WORKDIR /build
4
+ COPY web/package.json .
5
+ COPY web/bun.lock .
6
+ RUN bun install
7
+ COPY ./web .
8
+ COPY ./VERSION .
9
+ RUN DISABLE_ESLINT_PLUGIN='true' VITE_REACT_APP_VERSION=$(cat VERSION) bun run build
10
+
11
+ FROM golang:alpine AS builder2
12
+ ENV GO111MODULE=on CGO_ENABLED=0
13
+
14
+ ARG TARGETOS
15
+ ARG TARGETARCH
16
+ ENV GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH:-amd64}
17
+
18
+ WORKDIR /build
19
+
20
+ ADD go.mod go.sum ./
21
+ RUN go mod download
22
+
23
+ COPY . .
24
+ COPY --from=builder /build/dist ./web/dist
25
+ RUN go build -ldflags "-s -w -X 'github.com/QuantumNous/new-api/common.Version=$(cat VERSION)'" -o new-api
26
+
27
+ FROM alpine
28
+
29
+ RUN apk upgrade --no-cache \
30
+ && apk add --no-cache ca-certificates tzdata \
31
+ && update-ca-certificates
32
+
33
+ # Create a non-root user for HuggingFace Spaces
34
+ RUN adduser -D -u 1000 user
35
+ USER user
36
+
37
+ COPY --from=builder2 /build/new-api /new-api
38
+
39
+ # HuggingFace Spaces requires port 7860
40
+ ENV PORT=7860
41
+ EXPOSE 7860
42
+
43
+ WORKDIR /home/user/data
44
+ ENTRYPOINT ["/new-api"]
Dockerfile.hf ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM oven/bun:latest AS builder
2
+
3
+ WORKDIR /build
4
+ COPY web/package.json .
5
+ COPY web/bun.lock .
6
+ RUN bun install
7
+ COPY ./web .
8
+ COPY ./VERSION .
9
+ RUN DISABLE_ESLINT_PLUGIN='true' VITE_REACT_APP_VERSION=$(cat VERSION) bun run build
10
+
11
+ FROM golang:alpine AS builder2
12
+ ENV GO111MODULE=on CGO_ENABLED=0
13
+
14
+ ARG TARGETOS
15
+ ARG TARGETARCH
16
+ ENV GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH:-amd64}
17
+
18
+ WORKDIR /build
19
+
20
+ ADD go.mod go.sum ./
21
+ RUN go mod download
22
+
23
+ COPY . .
24
+ COPY --from=builder /build/dist ./web/dist
25
+ RUN go build -ldflags "-s -w -X 'github.com/QuantumNous/new-api/common.Version=$(cat VERSION)'" -o new-api
26
+
27
+ FROM alpine
28
+
29
+ RUN apk upgrade --no-cache \
30
+ && apk add --no-cache ca-certificates tzdata \
31
+ && update-ca-certificates
32
+
33
+ # Create a non-root user for HuggingFace Spaces
34
+ RUN adduser -D -u 1000 user
35
+ USER user
36
+
37
+ COPY --from=builder2 /build/new-api /new-api
38
+
39
+ # HuggingFace Spaces requires port 7860
40
+ ENV PORT=7860
41
+ EXPOSE 7860
42
+
43
+ WORKDIR /home/user/data
44
+ ENTRYPOINT ["/new-api"]
LICENSE ADDED
@@ -0,0 +1,103 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # **New API 许可协议 (Licensing)**
2
+
3
+ 本项目采用**基于使用场景的双重许可 (Usage-Based Dual Licensing)** 模式。
4
+
5
+ **核心原则:**
6
+
7
+ - **默认许可:** 本项目默认在 **GNU Affero 通用公共许可证 v3.0 (AGPLv3)** 下提供。任何用户在遵守 AGPLv3 条款和下述附加限制的前提下,均可免费使用。
8
+ - **商业许可:** 在特定商业场景下,或当您希望获得 AGPLv3 之外的权利时,**必须**获取**商业许可证 (Commercial License)**。
9
+
10
+ ---
11
+
12
+ ## **1. 开源许可证 (Open Source License): AGPLv3 - 适用于基础使用**
13
+
14
+ - 在遵守 **AGPLv3** 条款的前提下,您可以自由地使用、修改和分发 New API。AGPLv3 的完整文本可以访问 [https://www.gnu.org/licenses/agpl-3.0.html](https://www.gnu.org/licenses/agpl-3.0.html) 获取。
15
+ - **核心义务:** AGPLv3 的一个关键要求是,如果您修改了 New API 并通过网络提供服务 (SaaS),或者分发了修改后的版本,您必须以 AGPLv3 许可证向所有用户提供相应的**完整源代码**。
16
+ - **附加限制 (重要):** 在仅使用 AGPLv3 开源许可证的情况下,您**必须**完整保留项目代码中原有的品牌标识、LOGO 及版权声明信息。**禁止以任何形式修改、移除或遮盖**这些信息。如需移除,必须获取商业许可证。
17
+ - 使用前请务必仔细阅读并理解 AGPLv3 的所有条款及上述附加限制。
18
+
19
+ ## **2. 商业许可证 (Commercial License) - 适用于高级场景及闭源需求**
20
+
21
+ 在以下任一情况下,您**必须**联系我们获取并签署一份商业许可证,才能合法使用 New API:
22
+
23
+ - **场景一:移除品牌和版权信息**
24
+ 您希望在您的产品或服务中移除 New API 的 LOGO、UI界面中的版权声明或其他品牌标识。
25
+
26
+ - **场景二:规避 AGPLv3 开源义务**
27
+ 您基于 New API 进行了修改,并希望:
28
+ - 通过网络提供服务(SaaS),但**不希望**向您的服务用户公开您修改后的源代码。
29
+ - 分发一个集成了 New API 的软件产品,但**不希望**以 AGPLv3 许可证发布您的产品或公开源代码。
30
+
31
+ - **场景三:企业政策与集成需求**
32
+ - 您所在公司的政策、客户合同或项目要求不允许使用 AGPLv3 许可的软件。
33
+ - 您需要进行 OEM 集成,将 New API 作为您闭源商业产品的一部分进行再分发。
34
+
35
+ - **场景四:需要商业支持与保障**
36
+ 您需要 AGPLv3 未提供的商业保障,如官方技术支持等。
37
+
38
+ **获取商业许可:**
39
+ 请通过电子邮件 **support@quantumnous.com** 联系 New API 团队洽谈商业授权事宜。
40
+
41
+ ## **3. 贡献 (Contributions)**
42
+
43
+ - 我们欢迎社区对 New API 的贡献。所有向本项目提交的贡献(例如通过 Pull Request)都将被视为在 **AGPLv3** 许可证下提供。
44
+ - 通过向本项目提交贡献,即表示您同意您的代码以 AGPLv3 许可证授权给本项目及所有后续使用者(无论这些使用者最终遵循 AGPLv3 还是商业许可)。
45
+ - 您也理解并同意,您的贡献可能会被包含在根据商业许可证分发的 New API 版本中。
46
+
47
+ ## **4. 其他条款 (Other Terms)**
48
+
49
+ - 关于商业许可证的具体条款、条件和价格,以双方签署的正式商业许可协议为准。
50
+ - 项目维护者保留根据需要更新本许可政策的权利。相关更新将通过项目官方渠道(如代码仓库、官方网站)进行通知。
51
+
52
+ ---
53
+
54
+ # **New API Licensing**
55
+
56
+ This project uses a **Usage-Based Dual Licensing** model.
57
+
58
+ **Core Principles:**
59
+
60
+ - **Default License:** This project is available by default under the **GNU Affero General Public License v3.0 (AGPLv3)**. Any user may use it free of charge, provided they comply with both the AGPLv3 terms and the additional restrictions listed below.
61
+ - **Commercial License:** For specific commercial scenarios, or if you require rights beyond those granted by AGPLv3, you **must** obtain a **Commercial License**.
62
+
63
+ ---
64
+
65
+ ## **1. Open Source License: AGPLv3 – For Basic Usage**
66
+
67
+ - Under the terms of the **AGPLv3**, you are free to use, modify, and distribute New API. The complete AGPLv3 license text can be viewed at [https://www.gnu.org/licenses/agpl-3.0.html](https://www.gnu.org/licenses/agpl-3.0.html).
68
+ - **Core Obligation:** A key AGPLv3 requirement is that if you modify New API and provide it as a network service (SaaS), or distribute a modified version, you must make the **complete corresponding source code** available to all users under the AGPLv3 license.
69
+ - **Additional Restriction (Important):** When using only the AGPLv3 open-source license, you **must** retain all original branding, logos, and copyright statements within the project’s code. **You are strictly prohibited from modifying, removing, or concealing** any such information. If you wish to remove this, you must obtain a Commercial License.
70
+ - Please read and ensure that you fully understand all AGPLv3 terms and the above additional restriction before use.
71
+
72
+ ## **2. Commercial License – For Advanced Scenarios & Closed Source Needs**
73
+
74
+ You **must** contact us to obtain and sign a Commercial License in any of the following scenarios in order to legally use New API:
75
+
76
+ - **Scenario 1: Removal of Branding and Copyright**
77
+ You wish to remove the New API logo, copyright statement, or other branding elements from your product or service.
78
+
79
+ - **Scenario 2: Avoidance of AGPLv3 Open Source Obligations**
80
+ You have modified New API and wish to:
81
+ - Offer it as a network service (SaaS) **without** disclosing your modifications' source code to your users.
82
+ - Distribute a software product integrated with New API **without** releasing your product under AGPLv3 or open-sourcing the code.
83
+
84
+ - **Scenario 3: Enterprise Policy & Integration Needs**
85
+ - Your organization’s policies, client contracts, or project requirements prohibit the use of AGPLv3-licensed software.
86
+ - You require OEM integration and need to redistribute New API as part of your closed-source commercial product.
87
+
88
+ - **Scenario 4: Commercial Support and Assurances**
89
+ You require commercial assurances not provided by AGPLv3, such as official technical support.
90
+
91
+ **Obtaining a Commercial License:**
92
+ Please contact the New API team via email at **support@quantumnous.com** to discuss commercial licensing.
93
+
94
+ ## **3. Contributions**
95
+
96
+ - We welcome community contributions to New API. All contributions (e.g., via Pull Request) are deemed to be provided under the **AGPLv3** license.
97
+ - By submitting a contribution, you agree that your code is licensed to this project and all downstream users under the AGPLv3 license (regardless of whether those users ultimately operate under AGPLv3 or a Commercial License).
98
+ - You also acknowledge and agree that your contribution may be included in New API releases distributed under a Commercial License.
99
+
100
+ ## **4. Other Terms**
101
+
102
+ - The specific terms, conditions, and pricing of the Commercial License are governed by the formal commercial license agreement executed by both parties.
103
+ - Project maintainers reserve the right to update this licensing policy as needed. Updates will be communicated via official project channels (e.g., repository, official website).
README.en.md ADDED
@@ -0,0 +1,447 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <div align="center">
2
+
3
+ ![new-api](/web/public/logo.png)
4
+
5
+ # New API
6
+
7
+ 🍥 **Next-Generation Large Model Gateway and AI Asset Management System**
8
+
9
+ <p align="center">
10
+ <a href="./README.md">中文</a> |
11
+ <strong>English</strong> |
12
+ <a href="./README.fr.md">Français</a> |
13
+ <a href="./README.ja.md">日本語</a>
14
+ </p>
15
+
16
+ <p align="center">
17
+ <a href="https://raw.githubusercontent.com/Calcium-Ion/new-api/main/LICENSE">
18
+ <img src="https://img.shields.io/github/license/Calcium-Ion/new-api?color=brightgreen" alt="license">
19
+ </a>
20
+ <a href="https://github.com/Calcium-Ion/new-api/releases/latest">
21
+ <img src="https://img.shields.io/github/v/release/Calcium-Ion/new-api?color=brightgreen&include_prereleases" alt="release">
22
+ </a>
23
+ <a href="https://github.com/users/Calcium-Ion/packages/container/package/new-api">
24
+ <img src="https://img.shields.io/badge/docker-ghcr.io-blue" alt="docker">
25
+ </a>
26
+ <a href="https://hub.docker.com/r/CalciumIon/new-api">
27
+ <img src="https://img.shields.io/badge/docker-dockerHub-blue" alt="docker">
28
+ </a>
29
+ <a href="https://goreportcard.com/report/github.com/Calcium-Ion/new-api">
30
+ <img src="https://goreportcard.com/badge/github.com/Calcium-Ion/new-api" alt="GoReportCard">
31
+ </a>
32
+ </p>
33
+
34
+ <p align="center">
35
+ <a href="https://trendshift.io/repositories/8227" target="_blank">
36
+ <img src="https://trendshift.io/api/badge/repositories/8227" alt="Calcium-Ion%2Fnew-api | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/>
37
+ </a>
38
+ </p>
39
+
40
+ <p align="center">
41
+ <a href="#-quick-start">Quick Start</a> •
42
+ <a href="#-key-features">Key Features</a> •
43
+ <a href="#-deployment">Deployment</a> •
44
+ <a href="#-documentation">Documentation</a> •
45
+ <a href="#-help-support">Help</a>
46
+ </p>
47
+
48
+ </div>
49
+
50
+ ## 📝 Project Description
51
+
52
+ > [!NOTE]
53
+ > This is an open-source project developed based on [One API](https://github.com/songquanpeng/one-api)
54
+
55
+ > [!IMPORTANT]
56
+ > - This project is for personal learning purposes only, with no guarantee of stability or technical support
57
+ > - Users must comply with OpenAI's [Terms of Use](https://openai.com/policies/terms-of-use) and **applicable laws and regulations**, and must not use it for illegal purposes
58
+ > - According to the [《Interim Measures for the Management of Generative Artificial Intelligence Services》](http://www.cac.gov.cn/2023-07/13/c_1690898327029107.htm), please do not provide any unregistered generative AI services to the public in China.
59
+
60
+ ---
61
+
62
+ ## 🤝 Trusted Partners
63
+
64
+ <p align="center">
65
+ <em>No particular order</em>
66
+ </p>
67
+
68
+ <p align="center">
69
+ <a href="https://www.cherry-ai.com/" target="_blank">
70
+ <img src="./docs/images/cherry-studio.png" alt="Cherry Studio" height="80" />
71
+ </a>
72
+ <a href="https://bda.pku.edu.cn/" target="_blank">
73
+ <img src="./docs/images/pku.png" alt="Peking University" height="80" />
74
+ </a>
75
+ <a href="https://www.compshare.cn/?ytag=GPU_yy_gh_newapi" target="_blank">
76
+ <img src="./docs/images/ucloud.png" alt="UCloud" height="80" />
77
+ </a>
78
+ <a href="https://www.aliyun.com/" target="_blank">
79
+ <img src="./docs/images/aliyun.png" alt="Alibaba Cloud" height="80" />
80
+ </a>
81
+ <a href="https://io.net/" target="_blank">
82
+ <img src="./docs/images/io-net.png" alt="IO.NET" height="80" />
83
+ </a>
84
+ </p>
85
+
86
+ ---
87
+
88
+ ## 🙏 Special Thanks
89
+
90
+ <p align="center">
91
+ <a href="https://www.jetbrains.com/?from=new-api" target="_blank">
92
+ <img src="https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.png" alt="JetBrains Logo" width="120" />
93
+ </a>
94
+ </p>
95
+
96
+ <p align="center">
97
+ <strong>Thanks to <a href="https://www.jetbrains.com/?from=new-api">JetBrains</a> for providing free open-source development license for this project</strong>
98
+ </p>
99
+
100
+ ---
101
+
102
+ ## 🚀 Quick Start
103
+
104
+ ### Using Docker Compose (Recommended)
105
+
106
+ ```bash
107
+ # Clone the project
108
+ git clone https://github.com/QuantumNous/new-api.git
109
+ cd new-api
110
+
111
+ # Edit docker-compose.yml configuration
112
+ nano docker-compose.yml
113
+
114
+ # Start the service
115
+ docker-compose up -d
116
+ ```
117
+
118
+ <details>
119
+ <summary><strong>Using Docker Commands</strong></summary>
120
+
121
+ ```bash
122
+ # Pull the latest image
123
+ docker pull calciumion/new-api:latest
124
+
125
+ # Using SQLite (default)
126
+ docker run --name new-api -d --restart always \
127
+ -p 3000:3000 \
128
+ -e TZ=Asia/Shanghai \
129
+ -v ./data:/data \
130
+ calciumion/new-api:latest
131
+
132
+ # Using MySQL
133
+ docker run --name new-api -d --restart always \
134
+ -p 3000:3000 \
135
+ -e SQL_DSN="root:123456@tcp(localhost:3306)/oneapi" \
136
+ -e TZ=Asia/Shanghai \
137
+ -v ./data:/data \
138
+ calciumion/new-api:latest
139
+ ```
140
+
141
+ > **💡 Tip:** `-v ./data:/data` will save data in the `data` folder of the current directory, you can also change it to an absolute path like `-v /your/custom/path:/data`
142
+
143
+ </details>
144
+
145
+ ---
146
+
147
+ 🎉 After deployment is complete, visit `http://localhost:3000` to start using!
148
+
149
+ 📖 For more deployment methods, please refer to [Deployment Guide](https://docs.newapi.pro/installation)
150
+
151
+ ---
152
+
153
+ ## 📚 Documentation
154
+
155
+ <div align="center">
156
+
157
+ ### 📖 [Official Documentation](https://docs.newapi.pro/) | [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/QuantumNous/new-api)
158
+
159
+ </div>
160
+
161
+ **Quick Navigation:**
162
+
163
+ | Category | Link |
164
+ |------|------|
165
+ | 🚀 Deployment Guide | [Installation Documentation](https://docs.newapi.pro/installation) |
166
+ | ⚙️ Environment Configuration | [Environment Variables](https://docs.newapi.pro/installation/environment-variables) |
167
+ | 📡 API Documentation | [API Documentation](https://docs.newapi.pro/api) |
168
+ | ❓ FAQ | [FAQ](https://docs.newapi.pro/support/faq) |
169
+ | 💬 Community Interaction | [Communication Channels](https://docs.newapi.pro/support/community-interaction) |
170
+
171
+ ---
172
+
173
+ ## ✨ Key Features
174
+
175
+ > For detailed features, please refer to [Features Introduction](https://docs.newapi.pro/wiki/features-introduction)
176
+
177
+ ### 🎨 Core Functions
178
+
179
+ | Feature | Description |
180
+ |------|------|
181
+ | 🎨 New UI | Modern user interface design |
182
+ | 🌍 Multi-language | Supports Chinese, English, French, Japanese |
183
+ | 🔄 Data Compatibility | Fully compatible with the original One API database |
184
+ | 📈 Data Dashboard | Visual console and statistical analysis |
185
+ | 🔒 Permission Management | Token grouping, model restrictions, user management |
186
+
187
+ ### 💰 Payment and Billing
188
+
189
+ - ✅ Online recharge (EPay, Stripe)
190
+ - ✅ Pay-per-use model pricing
191
+ - ✅ Cache billing support (OpenAI, Azure, DeepSeek, Claude, Qwen and all supported models)
192
+ - ✅ Flexible billing policy configuration
193
+
194
+ ### 🔐 Authorization and Security
195
+
196
+ - 😈 Discord authorization login
197
+ - 🤖 LinuxDO authorization login
198
+ - 📱 Telegram authorization login
199
+ - 🔑 OIDC unified authentication
200
+
201
+ ### 🚀 Advanced Features
202
+
203
+ **API Format Support:**
204
+ - ⚡ [OpenAI Responses](https://docs.newapi.pro/api/openai-responses)
205
+ - ⚡ [OpenAI Realtime API](https://docs.newapi.pro/api/openai-realtime) (including Azure)
206
+ - ⚡ [Claude Messages](https://docs.newapi.pro/api/anthropic-chat)
207
+ - ⚡ [Google Gemini](https://docs.newapi.pro/api/google-gemini-chat/)
208
+ - 🔄 [Rerank Models](https://docs.newapi.pro/api/jinaai-rerank) (Cohere, Jina)
209
+
210
+ **Intelligent Routing:**
211
+ - ⚖️ Channel weighted random
212
+ - 🔄 Automatic retry on failure
213
+ - 🚦 User-level model rate limiting
214
+
215
+ **Format Conversion:**
216
+ - 🔄 OpenAI ⇄ Claude Messages
217
+ - 🔄 OpenAI ⇄ Gemini Chat
218
+ - 🔄 Thinking-to-content functionality
219
+
220
+ **Reasoning Effort Support:**
221
+
222
+ <details>
223
+ <summary>View detailed configuration</summary>
224
+
225
+ **OpenAI series models:**
226
+ - `o3-mini-high` - High reasoning effort
227
+ - `o3-mini-medium` - Medium reasoning effort
228
+ - `o3-mini-low` - Low reasoning effort
229
+ - `gpt-5-high` - High reasoning effort
230
+ - `gpt-5-medium` - Medium reasoning effort
231
+ - `gpt-5-low` - Low reasoning effort
232
+
233
+ **Claude thinking models:**
234
+ - `claude-3-7-sonnet-20250219-thinking` - Enable thinking mode
235
+
236
+ **Google Gemini series models:**
237
+ - `gemini-2.5-flash-thinking` - Enable thinking mode
238
+ - `gemini-2.5-flash-nothinking` - Disable thinking mode
239
+ - `gemini-2.5-pro-thinking` - Enable thinking mode
240
+ - `gemini-2.5-pro-thinking-128` - Enable thinking mode with thinking budget of 128 tokens
241
+
242
+ </details>
243
+
244
+ ---
245
+
246
+ ## 🤖 Model Support
247
+
248
+ > For details, please refer to [API Documentation - Relay Interface](https://docs.newapi.pro/api)
249
+
250
+ | Model Type | Description | Documentation |
251
+ |---------|------|------|
252
+ | 🤖 OpenAI GPTs | gpt-4-gizmo-* series | - |
253
+ | 🎨 Midjourney-Proxy | [Midjourney-Proxy(Plus)](https://github.com/novicezk/midjourney-proxy) | [Documentation](https://docs.newapi.pro/api/midjourney-proxy-image) |
254
+ | 🎵 Suno-API | [Suno API](https://github.com/Suno-API/Suno-API) | [Documentation](https://docs.newapi.pro/api/suno-music) |
255
+ | 🔄 Rerank | Cohere, Jina | [Documentation](https://docs.newapi.pro/api/jinaai-rerank) |
256
+ | 💬 Claude | Messages format | [Documentation](https://docs.newapi.pro/api/anthropic-chat) |
257
+ | 🌐 Gemini | Google Gemini format | [Documentation](https://docs.newapi.pro/api/google-gemini-chat/) |
258
+ | 🔧 Dify | ChatFlow mode | - |
259
+ | 🎯 Custom | Supports complete call address | - |
260
+
261
+ ### 📡 Supported Interfaces
262
+
263
+ <details>
264
+ <summary>View complete interface list</summary>
265
+
266
+ - [Chat Interface (Chat Completions)](https://docs.newapi.pro/api/openai-chat)
267
+ - [Response Interface (Responses)](https://docs.newapi.pro/api/openai-responses)
268
+ - [Image Interface (Image)](https://docs.newapi.pro/api/openai-image)
269
+ - [Audio Interface (Audio)](https://docs.newapi.pro/api/openai-audio)
270
+ - [Video Interface (Video)](https://docs.newapi.pro/api/openai-video)
271
+ - [Embedding Interface (Embeddings)](https://docs.newapi.pro/api/openai-embeddings)
272
+ - [Rerank Interface (Rerank)](https://docs.newapi.pro/api/jinaai-rerank)
273
+ - [Realtime Conversation (Realtime)](https://docs.newapi.pro/api/openai-realtime)
274
+ - [Claude Chat](https://docs.newapi.pro/api/anthropic-chat)
275
+ - [Google Gemini Chat](https://docs.newapi.pro/api/google-gemini-chat/)
276
+
277
+ </details>
278
+
279
+ ---
280
+
281
+ ## 🚢 Deployment
282
+
283
+ > [!TIP]
284
+ > **Latest Docker image:** `calciumion/new-api:latest`
285
+
286
+ ### 📋 Deployment Requirements
287
+
288
+ | Component | Requirement |
289
+ |------|------|
290
+ | **Local database** | SQLite (Docker must mount `/data` directory)|
291
+ | **Remote database** | MySQL ≥ 5.7.8 or PostgreSQL ≥ 9.6 |
292
+ | **Container engine** | Docker / Docker Compose |
293
+
294
+ ### ⚙️ Environment Variable Configuration
295
+
296
+ <details>
297
+ <summary>Common environment variable configuration</summary>
298
+
299
+ | Variable Name | Description | Default Value |
300
+ |--------|------|--------|
301
+ | `SESSION_SECRET` | Session secret (required for multi-machine deployment) | - |
302
+ | `CRYPTO_SECRET` | Encryption secret (required for Redis) | - |
303
+ | `SQL_DSN` | Database connection string | - |
304
+ | `REDIS_CONN_STRING` | Redis connection string | - |
305
+ | `STREAMING_TIMEOUT` | Streaming timeout (seconds) | `300` |
306
+ | `AZURE_DEFAULT_API_VERSION` | Azure API version | `2025-04-01-preview` |
307
+ | `ERROR_LOG_ENABLED` | Error log switch | `false` |
308
+
309
+ 📖 **Complete configuration:** [Environment Variables Documentation](https://docs.newapi.pro/installation/environment-variables)
310
+
311
+ </details>
312
+
313
+ ### 🔧 Deployment Methods
314
+
315
+ <details>
316
+ <summary><strong>Method 1: Docker Compose (Recommended)</strong></summary>
317
+
318
+ ```bash
319
+ # Clone the project
320
+ git clone https://github.com/QuantumNous/new-api.git
321
+ cd new-api
322
+
323
+ # Edit configuration
324
+ nano docker-compose.yml
325
+
326
+ # Start service
327
+ docker-compose up -d
328
+ ```
329
+
330
+ </details>
331
+
332
+ <details>
333
+ <summary><strong>Method 2: Docker Commands</strong></summary>
334
+
335
+ **Using SQLite:**
336
+ ```bash
337
+ docker run --name new-api -d --restart always \
338
+ -p 3000:3000 \
339
+ -e TZ=Asia/Shanghai \
340
+ -v ./data:/data \
341
+ calciumion/new-api:latest
342
+ ```
343
+
344
+ **Using MySQL:**
345
+ ```bash
346
+ docker run --name new-api -d --restart always \
347
+ -p 3000:3000 \
348
+ -e SQL_DSN="root:123456@tcp(localhost:3306)/oneapi" \
349
+ -e TZ=Asia/Shanghai \
350
+ -v ./data:/data \
351
+ calciumion/new-api:latest
352
+ ```
353
+
354
+ > **💡 Path explanation:**
355
+ > - `./data:/data` - Relative path, data saved in the data folder of the current directory
356
+ > - You can also use absolute path, e.g.: `/your/custom/path:/data`
357
+
358
+ </details>
359
+
360
+ <details>
361
+ <summary><strong>Method 3: BaoTa Panel</strong></summary>
362
+
363
+ 1. Install BaoTa Panel (≥ 9.2.0 version)
364
+ 2. Search for **New-API** in the application store
365
+ 3. One-click installation
366
+
367
+ 📖 [Tutorial with images](./docs/BT.md)
368
+
369
+ </details>
370
+
371
+ ### ⚠️ Multi-machine Deployment Considerations
372
+
373
+ > [!WARNING]
374
+ > - **Must set** `SESSION_SECRET` - Otherwise login status inconsistent
375
+ > - **Shared Redis must set** `CRYPTO_SECRET` - Otherwise data cannot be decrypted
376
+
377
+ ### 🔄 Channel Retry and Cache
378
+
379
+ **Retry configuration:** `Settings → Operation Settings → General Settings → Failure Retry Count`
380
+
381
+ **Cache configuration:**
382
+ - `REDIS_CONN_STRING`: Redis cache (recommended)
383
+ - `MEMORY_CACHE_ENABLED`: Memory cache
384
+
385
+ ---
386
+
387
+ ## 🔗 Related Projects
388
+
389
+ ### Upstream Projects
390
+
391
+ | Project | Description |
392
+ |------|------|
393
+ | [One API](https://github.com/songquanpeng/one-api) | Original project base |
394
+ | [Midjourney-Proxy](https://github.com/novicezk/midjourney-proxy) | Midjourney interface support |
395
+
396
+ ### Supporting Tools
397
+
398
+ | Project | Description |
399
+ |------|------|
400
+ | [neko-api-key-tool](https://github.com/Calcium-Ion/neko-api-key-tool) | Key quota query tool |
401
+ | [new-api-horizon](https://github.com/Calcium-Ion/new-api-horizon) | New API high-performance optimized version |
402
+
403
+ ---
404
+
405
+ ## 💬 Help Support
406
+
407
+ ### 📖 Documentation Resources
408
+
409
+ | Resource | Link |
410
+ |------|------|
411
+ | 📘 FAQ | [FAQ](https://docs.newapi.pro/support/faq) |
412
+ | 💬 Community Interaction | [Communication Channels](https://docs.newapi.pro/support/community-interaction) |
413
+ | 🐛 Issue Feedback | [Issue Feedback](https://docs.newapi.pro/support/feedback-issues) |
414
+ | 📚 Complete Documentation | [Official Documentation](https://docs.newapi.pro/support) |
415
+
416
+ ### 🤝 Contribution Guide
417
+
418
+ Welcome all forms of contribution!
419
+
420
+ - 🐛 Report Bugs
421
+ - 💡 Propose New Features
422
+ - 📝 Improve Documentation
423
+ - 🔧 Submit Code
424
+
425
+ ---
426
+
427
+ ## 🌟 Star History
428
+
429
+ <div align="center">
430
+
431
+ [![Star History Chart](https://api.star-history.com/svg?repos=Calcium-Ion/new-api&type=Date)](https://star-history.com/#Calcium-Ion/new-api&Date)
432
+
433
+ </div>
434
+
435
+ ---
436
+
437
+ <div align="center">
438
+
439
+ ### 💖 Thank you for using New API
440
+
441
+ If this project is helpful to you, welcome to give us a ⭐️ Star!
442
+
443
+ **[Official Documentation](https://docs.newapi.pro/)** • **[Issue Feedback](https://github.com/Calcium-Ion/new-api/issues)** • **[Latest Release](https://github.com/Calcium-Ion/new-api/releases)**
444
+
445
+ <sub>Built with ❤️ by QuantumNous</sub>
446
+
447
+ </div>
README.fr.md ADDED
@@ -0,0 +1,441 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <div align="center">
2
+
3
+ ![new-api](/web/public/logo.png)
4
+
5
+ # New API
6
+
7
+ 🍥 **Passerelle de modèles étendus de nouvelle génération et système de gestion d'actifs d'IA**
8
+
9
+ <p align="center">
10
+ <a href="./README.md">中文</a> |
11
+ <a href="./README.en.md">English</a> |
12
+ <strong>Français</strong> |
13
+ <a href="./README.ja.md">日本語</a>
14
+ </p>
15
+
16
+ <p align="center">
17
+ <a href="https://raw.githubusercontent.com/Calcium-Ion/new-api/main/LICENSE">
18
+ <img src="https://img.shields.io/github/license/Calcium-Ion/new-api?color=brightgreen" alt="licence">
19
+ </a>
20
+ <a href="https://github.com/Calcium-Ion/new-api/releases/latest">
21
+ <img src="https://img.shields.io/github/v/release/Calcium-Ion/new-api?color=brightgreen&include_prereleases" alt="version">
22
+ </a>
23
+ <a href="https://github.com/users/Calcium-Ion/packages/container/package/new-api">
24
+ <img src="https://img.shields.io/badge/docker-ghcr.io-blue" alt="docker">
25
+ </a>
26
+ <a href="https://hub.docker.com/r/CalciumIon/new-api">
27
+ <img src="https://img.shields.io/badge/docker-dockerHub-blue" alt="docker">
28
+ </a>
29
+ <a href="https://goreportcard.com/report/github.com/Calcium-Ion/new-api">
30
+ <img src="https://goreportcard.com/badge/github.com/Calcium-Ion/new-api" alt="GoReportCard">
31
+ </a>
32
+ </p>
33
+
34
+ <p align="center">
35
+ <a href="https://trendshift.io/repositories/8227" target="_blank">
36
+ <img src="https://trendshift.io/api/badge/repositories/8227" alt="Calcium-Ion%2Fnew-api | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/>
37
+ </a>
38
+ </p>
39
+
40
+ <p align="center">
41
+ <a href="#-démarrage-rapide">Démarrage rapide</a> •
42
+ <a href="#-fonctionnalités-clés">Fonctionnalités clés</a> •
43
+ <a href="#-déploiement">Déploiement</a> •
44
+ <a href="#-documentation">Documentation</a> •
45
+ <a href="#-aide-support">Aide</a>
46
+ </p>
47
+
48
+ </div>
49
+
50
+ ## 📝 Description du projet
51
+
52
+ > [!NOTE]
53
+ > Il s'agit d'un projet open-source développé sur la base de [One API](https://github.com/songquanpeng/one-api)
54
+
55
+ > [!IMPORTANT]
56
+ > - Ce projet est uniquement destiné à des fins d'apprentissage personnel, sans garantie de stabilité ni de support technique.
57
+ > - Les utilisateurs doivent se conformer aux [Conditions d'utilisation](https://openai.com/policies/terms-of-use) d'OpenAI et aux **lois et réglementations applicables**, et ne doivent pas l'utiliser à des fins illégales.
58
+ > - Conformément aux [《Mesures provisoires pour la gestion des services d'intelligence artificielle générative》](http://www.cac.gov.cn/2023-07/13/c_1690898327029107.htm), veuillez ne fournir aucun service d'IA générative non enregistré au public en Chine.
59
+
60
+ ---
61
+
62
+ ## 🤝 Partenaires de confiance
63
+
64
+ <p align="center">
65
+ <em>Sans ordre particulier</em>
66
+ </p>
67
+
68
+ <p align="center">
69
+ <a href="https://www.cherry-ai.com/" target="_blank">
70
+ <img src="./docs/images/cherry-studio.png" alt="Cherry Studio" height="80" />
71
+ </a>
72
+ <a href="https://bda.pku.edu.cn/" target="_blank">
73
+ <img src="./docs/images/pku.png" alt="Université de Pékin" height="80" />
74
+ </a>
75
+ <a href="https://www.compshare.cn/?ytag=GPU_yy_gh_newapi" target="_blank">
76
+ <img src="./docs/images/ucloud.png" alt="UCloud" height="80" />
77
+ </a>
78
+ <a href="https://www.aliyun.com/" target="_blank">
79
+ <img src="./docs/images/aliyun.png" alt="Alibaba Cloud" height="80" />
80
+ </a>
81
+ <a href="https://io.net/" target="_blank">
82
+ <img src="./docs/images/io-net.png" alt="IO.NET" height="80" />
83
+ </a>
84
+ </p>
85
+
86
+ ---
87
+
88
+ ## 🙏 Remerciements spéciaux
89
+
90
+ <p align="center">
91
+ <a href="https://www.jetbrains.com/?from=new-api" target="_blank">
92
+ <img src="https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.png" alt="JetBrains Logo" width="120" />
93
+ </a>
94
+ </p>
95
+
96
+ <p align="center">
97
+ <strong>Merci à <a href="https://www.jetbrains.com/?from=new-api">JetBrains</a> pour avoir fourni une licence de développement open-source gratuite pour ce projet</strong>
98
+ </p>
99
+
100
+ ---
101
+
102
+ ## 🚀 Démarrage rapide
103
+
104
+ ### Utilisation de Docker Compose (recommandé)
105
+
106
+ ```bash
107
+ # Cloner le projet
108
+ git clone https://github.com/QuantumNous/new-api.git
109
+ cd new-api
110
+
111
+ # Modifier la configuration docker-compose.yml
112
+ nano docker-compose.yml
113
+
114
+ # Démarrer le service
115
+ docker-compose up -d
116
+ ```
117
+
118
+ <details>
119
+ <summary><strong>Utilisation des commandes Docker</strong></summary>
120
+
121
+ ```bash
122
+ # Tirer la dernière image
123
+ docker pull calciumion/new-api:latest
124
+
125
+ # Utilisation de SQLite (par défaut)
126
+ docker run --name new-api -d --restart always \
127
+ -p 3000:3000 \
128
+ -e TZ=Asia/Shanghai \
129
+ -v ./data:/data \
130
+ calciumion/new-api:latest
131
+
132
+ # Utilisation de MySQL
133
+ docker run --name new-api -d --restart always \
134
+ -p 3000:3000 \
135
+ -e SQL_DSN="root:123456@tcp(localhost:3306)/oneapi" \
136
+ -e TZ=Asia/Shanghai \
137
+ -v ./data:/data \
138
+ calciumion/new-api:latest
139
+ ```
140
+
141
+ > **💡 Astuce:** `-v ./data:/data` sauvegardera les données dans le dossier `data` du répertoire actuel, vous pouvez également le changer en chemin absolu comme `-v /your/custom/path:/data`
142
+
143
+ </details>
144
+
145
+ ---
146
+
147
+ 🎉 Après le déploiement, visitez `http://localhost:3000` pour commencer à utiliser!
148
+
149
+ 📖 Pour plus de méthodes de déploiement, veuillez vous référer à [Guide de déploiement](https://docs.newapi.pro/installation)
150
+
151
+ ---
152
+
153
+ ## 📚 Documentation
154
+
155
+ <div align="center">
156
+
157
+ ### 📖 [Documentation officielle](https://docs.newapi.pro/) | [![Demander à DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/QuantumNous/new-api)
158
+
159
+ </div>
160
+
161
+ **Navigation rapide:**
162
+
163
+ | Catégorie | Lien |
164
+ |------|------|
165
+ | 🚀 Guide de déploiement | [Documentation d'installation](https://docs.newapi.pro/installation) |
166
+ | ⚙️ Configuration de l'environnement | [Variables d'environnement](https://docs.newapi.pro/installation/environment-variables) |
167
+ | 📡 Documentation de l'API | [Documentation de l'API](https://docs.newapi.pro/api) |
168
+ | ❓ FAQ | [FAQ](https://docs.newapi.pro/support/faq) |
169
+ | 💬 Interaction avec la communauté | [Canaux de communication](https://docs.newapi.pro/support/community-interaction) |
170
+
171
+ ---
172
+
173
+ ## ✨ Fonctionnalités clés
174
+
175
+ > Pour les fonctionnalités détaillées, veuillez vous référer à [Présentation des fonctionnalités](https://docs.newapi.pro/wiki/features-introduction) |
176
+
177
+ ### 🎨 Fonctions principales
178
+
179
+ | Fonctionnalité | Description |
180
+ |------|------|
181
+ | 🎨 Nouvelle interface utilisateur | Conception d'interface utilisateur moderne |
182
+ | 🌍 Multilingue | Prend en charge le chinois, l'anglais, le français, le japonais |
183
+ | 🔄 Compatibilité des données | Complètement compatible avec la base de données originale de One API |
184
+ | 📈 Tableau de bord des données | Console visuelle et analyse statistique |
185
+ | 🔒 Gestion des permissions | Regroupement de jetons, restrictions de modèles, gestion des utilisateurs |
186
+
187
+ ### 💰 Paiement et facturation
188
+
189
+ - ✅ Recharge en ligne (EPay, Stripe)
190
+ - ✅ Tarification des modèles de paiement à l'utilisation
191
+ - ✅ Prise en charge de la facturation du cache (OpenAI, Azure, DeepSeek, Claude, Qwen et tous les modèles pris en charge)
192
+ - ✅ Configuration flexible des politiques de facturation
193
+
194
+ ### 🔐 Autorisation et sécurité
195
+
196
+ - 🤖 Connexion par autorisation LinuxDO
197
+ - 📱 Connexion par autorisation Telegram
198
+ - 🔑 Authentification unifiée OIDC
199
+
200
+ ### 🚀 Fonctionnalités avancées
201
+
202
+ **Prise en charge des formats d'API:**
203
+ - ⚡ [OpenAI Responses](https://docs.newapi.pro/api/openai-responses)
204
+ - ⚡ [OpenAI Realtime API](https://docs.newapi.pro/api/openai-realtime) (y compris Azure)
205
+ - ⚡ [Claude Messages](https://docs.newapi.pro/api/anthropic-chat)
206
+ - ⚡ [Google Gemini](https://docs.newapi.pro/api/google-gemini-chat/)
207
+ - 🔄 [Modèles Rerank](https://docs.newapi.pro/api/jinaai-rerank) (Cohere, Jina)
208
+
209
+ **Routage intelligent:**
210
+ - ⚖️ Sélection aléatoire pondérée des canaux
211
+ - 🔄 Nouvelle tentative automatique en cas d'échec
212
+ - 🚦 Limitation du débit du modèle pour les utilisateurs
213
+
214
+ **Conversion de format:**
215
+ - 🔄 OpenAI ⇄ Claude Messages
216
+ - 🔄 OpenAI ⇄ Gemini Chat
217
+ - 🔄 Fonctionnalité de la pensée au contenu
218
+
219
+ **Prise en charge de l'effort de raisonnement:**
220
+
221
+ <details>
222
+ <summary>Voir la configuration détaillée</summary>
223
+
224
+ **Modèles de la série o d'OpenAI:**
225
+ - `o3-mini-high` - Effort de raisonnement élevé
226
+ - `o3-mini-medium` - Effort de raisonnement moyen
227
+ - `o3-mini-low` - Effort de raisonnement faible
228
+
229
+ **Modèles de pensée de Claude:**
230
+ - `claude-3-7-sonnet-20250219-thinking` - Activer le mode de pensée
231
+
232
+ **Modèles de la série Google Gemini:**
233
+ - `gemini-2.5-flash-thinking` - Activer le mode de pensée
234
+ - `gemini-2.5-flash-nothinking` - Désactiver le mode de pensée
235
+ - `gemini-2.5-pro-thinking` - Activer le mode de pensée
236
+ - `gemini-2.5-pro-thinking-128` - Activer le mode de pensée avec budget de pensée de 128 tokens
237
+
238
+ </details>
239
+
240
+ ---
241
+
242
+ ## 🤖 Prise en charge des modèles
243
+
244
+ > Pour les détails, veuillez vous référer à [Documentation de l'API - Interface de relais](https://docs.newapi.pro/api)
245
+
246
+ | Type de modèle | Description | Documentation |
247
+ |---------|------|------|
248
+ | 🤖 OpenAI GPTs | série gpt-4-gizmo-* | - |
249
+ | 🎨 Midjourney-Proxy | [Midjourney-Proxy(Plus)](https://github.com/novicezk/midjourney-proxy) | [Documentation](https://docs.newapi.pro/api/midjourney-proxy-image) |
250
+ | 🎵 Suno-API | [Suno API](https://github.com/Suno-API/Suno-API) | [Documentation](https://docs.newapi.pro/api/suno-music) |
251
+ | 🔄 Rerank | Cohere, Jina | [Documentation](https://docs.newapi.pro/api/jinaai-rerank) |
252
+ | 💬 Claude | Format Messages | [Documentation](https://docs.newapi.pro/api/anthropic-chat) |
253
+ | 🌐 Gemini | Format Google Gemini | [Documentation](https://docs.newapi.pro/api/google-gemini-chat/) |
254
+ | 🔧 Dify | Mode ChatFlow | - |
255
+ | 🎯 Personnalisé | Prise en charge de l'adresse d'appel complète | - |
256
+
257
+ ### 📡 Interfaces prises en charge
258
+
259
+ <details>
260
+ <summary>Voir la liste complète des interfaces</summary>
261
+
262
+ - [Interface de discussion (Chat Completions)](https://docs.newapi.pro/api/openai-chat)
263
+ - [Interface de réponse (Responses)](https://docs.newapi.pro/api/openai-responses)
264
+ - [Interface d'image (Image)](https://docs.newapi.pro/api/openai-image)
265
+ - [Interface audio (Audio)](https://docs.newapi.pro/api/openai-audio)
266
+ - [Interface vidéo (Video)](https://docs.newapi.pro/api/openai-video)
267
+ - [Interface d'incorporation (Embeddings)](https://docs.newapi.pro/api/openai-embeddings)
268
+ - [Interface de rerank (Rerank)](https://docs.newapi.pro/api/jinaai-rerank)
269
+ - [Conversation en temps réel (Realtime)](https://docs.newapi.pro/api/openai-realtime)
270
+ - [Discussion Claude](https://docs.newapi.pro/api/anthropic-chat)
271
+ - [Discussion Google Gemini](https://docs.newapi.pro/api/google-gemini-chat/)
272
+
273
+ </details>
274
+
275
+ ---
276
+
277
+ ## 🚢 Déploiement
278
+
279
+ > [!TIP]
280
+ > **Dernière image Docker:** `calciumion/new-api:latest`
281
+
282
+ ### 📋 Exigences de déploiement
283
+
284
+ | Composant | Exigence |
285
+ |------|------|
286
+ | **Base de données locale** | SQLite (Docker doit monter le répertoire `/data`)|
287
+ | **Base de données distante | MySQL ≥ 5.7.8 ou PostgreSQL ≥ 9.6 |
288
+ | **Moteur de conteneur** | Docker / Docker Compose |
289
+
290
+ ### ⚙️ Configuration des variables d'environnement
291
+
292
+ <details>
293
+ <summary>Configuration courante des variables d'environnement</summary>
294
+
295
+ | Nom de variable | Description | Valeur par défaut |
296
+ |--------|------|--------|
297
+ | `SESSION_SECRET` | Secret de session (requis pour le déploiement multi-machines) |
298
+ | `CRYPTO_SECRET` | Secret de chiffrement (requis pour Redis) | - |
299
+ | `SQL_DSN` | Chaine de connexion à la base de données | - |
300
+ | `REDIS_CONN_STRING` | Chaine de connexion Redis | - |
301
+ | `STREAMING_TIMEOUT` | Délai d'expiration du streaming (secondes) | `300` |
302
+ | `AZURE_DEFAULT_API_VERSION` | Version de l'API Azure | `2025-04-01-preview` |
303
+ | `ERROR_LOG_ENABLED` | Interrupteur du journal d'erreurs | `false` |
304
+
305
+ 📖 **Configuration complète:** [Documentation des variables d'environnement](https://docs.newapi.pro/installation/environment-variables)
306
+
307
+ </details>
308
+
309
+ ### 🔧 Méthodes de déploiement
310
+
311
+ <details>
312
+ <summary><strong>Méthode 1: Docker Compose (recommandé)</strong></summary>
313
+
314
+ ```bash
315
+ # Cloner le projet
316
+ git clone https://github.com/QuantumNous/new-api.git
317
+ cd new-api
318
+
319
+ # Modifier la configuration
320
+ nano docker-compose.yml
321
+
322
+ # Démarrer le service
323
+ docker-compose up -d
324
+ ```
325
+
326
+ </details>
327
+
328
+ <details>
329
+ <summary><strong>Méthode 2: Commandes Docker</strong></summary>
330
+
331
+ **Utilisation de SQLite:**
332
+ ```bash
333
+ docker run --name new-api -d --restart always \
334
+ -p 3000:3000 \
335
+ -e TZ=Asia/Shanghai \
336
+ -v ./data:/data \
337
+ calciumion/new-api:latest
338
+ ```
339
+
340
+ **Utilisation de MySQL:**
341
+ ```bash
342
+ docker run --name new-api -d --restart always \
343
+ -p 3000:3000 \
344
+ -e SQL_DSN="root:123456@tcp(localhost:3306)/oneapi" \
345
+ -e TZ=Asia/Shanghai \
346
+ -v ./data:/data \
347
+ calciumion/new-api:latest
348
+ ```
349
+
350
+ > **💡 Explication du chemin:**
351
+ > - `./data:/data` - Chemin relatif, données sauvegardées dans le dossier data du répertoire actuel
352
+ > - Vous pouvez également utiliser un chemin absolu, par exemple : `/your/custom/path:/data`
353
+
354
+ </details>
355
+
356
+ <details>
357
+ <summary><strong>Méthode 3: Panneau BaoTa</strong></summary>
358
+
359
+ 1. Installez le panneau BaoTa (version **9.2.0** ou supérieure), recherchez **New-API** dans le magasin d'applications et installez-le.
360
+ 2. Recherchez **New-API** dans le magasin d'applications et installez-le.
361
+
362
+ 📖 [Tutoriel avec des images](./docs/BT.md)
363
+
364
+ </details>
365
+
366
+ ### ⚠️ Considérations sur le déploiement multi-machines
367
+
368
+ > [!WARNING]
369
+ > - **Doit définir** `SESSION_SECRET` - Sinon l'état de connexion sera incohérent sur plusieurs machines
370
+ > - **Redis partagé doit définir** `CRYPTO_SECRET` - Sinon les données ne pourront pas être déchiffrées
371
+
372
+ ### 🔄 Nouvelle tentative de canal et cache
373
+
374
+ **Configuration de la nouvelle tentative:** `Paramètres → Paramètres de fonctionnement → Paramètres généraux → Nombre de tentatives en cas d'échec`
375
+
376
+ **Configuration du cache:**
377
+ - `REDIS_CONN_STRING`: Cache Redis (recommandé)
378
+ - `MEMORY_CACHE_ENABLED`: Cache mémoire
379
+
380
+ ---
381
+
382
+ ## 🔗 Projets connexes
383
+
384
+ ### Projets en amont
385
+
386
+ | Projet | Description |
387
+ |------|------|
388
+ | [One API](https://github.com/songquanpeng/one-api) | Base du projet original |
389
+ | [Midjourney-Proxy](https://github.com/novicezk/midjourney-proxy) | Prise en charge de l'interface Midjourney |
390
+
391
+ ### Outils d'accompagnement
392
+
393
+ | Projet | Description |
394
+ |------|------|
395
+ | [neko-api-key-tool](https://github.com/Calcium-Ion/neko-api-key-tool) | Outil de recherche de quota d'utilisation avec une clé |
396
+
397
+ ---
398
+
399
+ ## 💬 Aide et support
400
+
401
+ ### 📖 Ressources de documentation
402
+
403
+ | Ressource | Lien |
404
+ |------|------|
405
+ | 📘 FAQ | [FAQ](https://docs.newapi.pro/support/faq) |
406
+ | 💬 Interaction avec la communauté | [Canaux de communication](https://docs.newapi.pro/support/community-interaction) |
407
+ | 🐛 Commentaires sur les problèmes | [Commentaires sur les problèmes](https://docs.newapi.pro/support/feedback-issues) |
408
+ | 📚 Documentation complète | [Documentation officielle](https://docs.newapi.pro/support) |
409
+
410
+ ### 🤝 Guide de contribution
411
+
412
+ Bienvenue à toutes les formes de contribution!
413
+
414
+ - 🐛 Signaler des bogues
415
+ - 💡 Proposer de nouvelles fonctionnalités
416
+ - 📝 Améliorer la documentation
417
+ - 🔧 Soumettre du code
418
+
419
+ ---
420
+
421
+ ## 🌟 Historique des étoiles
422
+
423
+ <div align="center">
424
+
425
+ [![Graphique de l'historique des étoiles](https://api.star-history.com/svg?repos=Calcium-Ion/new-api&type=Date)](https://star-history.com/#Calcium-Ion/new-api&Date)
426
+
427
+ </div>
428
+
429
+ ---
430
+
431
+ <div align="center">
432
+
433
+ ### 💖 Merci d'utiliser New API
434
+
435
+ Si ce projet vous est utile, bienvenue à nous donner une ⭐️ Étoile!
436
+
437
+ **[Documentation officielle](https://docs.newapi.pro/)** • **[Commentaires sur les problèmes](https://github.com/Calcium-Ion/new-api/issues)** • **[Dernière version](https://github.com/Calcium-Ion/new-api/releases)**
438
+
439
+ <sub>Construit avec ❤️ par QuantumNous</sub>
440
+
441
+ </div>
README.ja.md ADDED
@@ -0,0 +1,450 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <div align="center">
2
+
3
+ ![new-api](/web/public/logo.png)
4
+
5
+ # New API
6
+
7
+ 🍥 **次世代大規模モデルゲートウェイとAI資産管理システム**
8
+
9
+ <p align="center">
10
+ <a href="./README.md">中文</a> |
11
+ <a href="./README.en.md">English</a> |
12
+ <a href="./README.fr.md">Français</a> |
13
+ <strong>日本語</strong>
14
+ </p>
15
+
16
+ <p align="center">
17
+ <a href="https://raw.githubusercontent.com/Calcium-Ion/new-api/main/LICENSE">
18
+ <img src="https://img.shields.io/github/license/Calcium-Ion/new-api?color=brightgreen" alt="license">
19
+ </a>
20
+ <a href="https://github.com/Calcium-Ion/new-api/releases/latest">
21
+ <img src="https://img.shields.io/github/v/release/Calcium-Ion/new-api?color=brightgreen&include_prereleases" alt="release">
22
+ </a>
23
+ <a href="https://github.com/users/Calcium-Ion/packages/container/package/new-api">
24
+ <img src="https://img.shields.io/badge/docker-ghcr.io-blue" alt="docker">
25
+ </a>
26
+ <a href="https://hub.docker.com/r/CalciumIon/new-api">
27
+ <img src="https://img.shields.io/badge/docker-dockerHub-blue" alt="docker">
28
+ </a>
29
+ <a href="https://goreportcard.com/report/github.com/Calcium-Ion/new-api">
30
+ <img src="https://goreportcard.com/badge/github.com/Calcium-Ion/new-api" alt="GoReportCard">
31
+ </a>
32
+ </p>
33
+
34
+ <p align="center">
35
+ <a href="https://trendshift.io/repositories/8227" target="_blank">
36
+ <img src="https://trendshift.io/api/badge/repositories/8227" alt="Calcium-Ion%2Fnew-api | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/>
37
+ </a>
38
+ </p>
39
+
40
+ <p align="center">
41
+ <a href="#-クイックスタート">クイックスタート</a> •
42
+ <a href="#-主な機能">主な機能</a> •
43
+ <a href="#-デプロイ">デプロイ</a> •
44
+ <a href="#-ドキュメント">ドキュメント</a> •
45
+ <a href="#-ヘルプサポート">ヘルプ</a>
46
+ </p>
47
+
48
+ </div>
49
+
50
+ ## 📝 プロジェクト説明
51
+
52
+ > [!NOTE]
53
+ > 本プロジェクトは、[One API](https://github.com/songquanpeng/one-api)をベースに二次開発されたオープンソースプロジェクトです
54
+
55
+ > [!IMPORTANT]
56
+ > - 本プロジェクトは個人学習用のみであり、安定性の保証や技術サポートは提供しません。
57
+ > - ユーザーは、OpenAIの[利用規約](https://openai.com/policies/terms-of-use)および**法律法規**を遵守する必要があり、違法な目的で使用してはいけません。
58
+ > - [《生成式人工智能服务管理暂行办法》](http://www.cac.gov.cn/2023-07/13/c_1690898327029107.htm)の要求に従い、中国地域の公衆に未登録の生成式AI サービスを提供しないでください。
59
+
60
+ ---
61
+
62
+ ## 🤝 信頼できるパートナー
63
+
64
+ <p align="center">
65
+ <em>順不同</em>
66
+ </p>
67
+
68
+ <p align="center">
69
+ <a href="https://www.cherry-ai.com/" target="_blank">
70
+ <img src="./docs/images/cherry-studio.png" alt="Cherry Studio" height="80" />
71
+ </a>
72
+ <a href="https://bda.pku.edu.cn/" target="_blank">
73
+ <img src="./docs/images/pku.png" alt="北京大学" height="80" />
74
+ </a>
75
+ <a href="https://www.compshare.cn/?ytag=GPU_yy_gh_newapi" target="_blank">
76
+ <img src="./docs/images/ucloud.png" alt="UCloud 優刻得" height="80" />
77
+ </a>
78
+ <a href="https://www.aliyun.com/" target="_blank">
79
+ <img src="./docs/images/aliyun.png" alt="Alibaba Cloud" height="80" />
80
+ </a>
81
+ <a href="https://io.net/" target="_blank">
82
+ <img src="./docs/images/io-net.png" alt="IO.NET" height="80" />
83
+ </a>
84
+ </p>
85
+
86
+ ---
87
+
88
+ ## 🙏 特別な感謝
89
+
90
+ <p align="center">
91
+ <a href="https://www.jetbrains.com/?from=new-api" target="_blank">
92
+ <img src="https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.png" alt="JetBrains Logo" width="120" />
93
+ </a>
94
+ </p>
95
+
96
+ <p align="center">
97
+ <strong>感謝 <a href="https://www.jetbrains.com/?from=new-api">JetBrains</a> が本プロジェクトに無料のオープンソース開発ライセンスを提供してくれたことに感謝します</strong>
98
+ </p>
99
+
100
+ ---
101
+
102
+ ## 🚀 クイックスタート
103
+
104
+ ### Docker Composeを使用(推奨)
105
+
106
+ ```bash
107
+ # プロジェクトをクローン
108
+ git clone https://github.com/QuantumNous/new-api.git
109
+ cd new-api
110
+
111
+ # docker-compose.yml 設定を編集
112
+ nano docker-compose.yml
113
+
114
+ # サービスを起動
115
+ docker-compose up -d
116
+ ```
117
+
118
+ <details>
119
+ <summary><strong>Dockerコマンドを使用</strong></summary>
120
+
121
+ ```bash
122
+ # 最新のイメージをプル
123
+ docker pull calciumion/new-api:latest
124
+
125
+ # SQLiteを使用(デフォルト)
126
+ docker run --name new-api -d --restart always \
127
+ -p 3000:3000 \
128
+ -e TZ=Asia/Shanghai \
129
+ -v ./data:/data \
130
+ calciumion/new-api:latest
131
+
132
+ # MySQLを使用
133
+ docker run --name new-api -d --restart always \
134
+ -p 3000:3000 \
135
+ -e SQL_DSN="root:123456@tcp(localhost:3306)/oneapi" \
136
+ -e TZ=Asia/Shanghai \
137
+ -v ./data:/data \
138
+ calciumion/new-api:latest
139
+ ```
140
+
141
+ > **💡 ヒント:** `-v ./data:/data` は現在のディレクトリの `data` フォルダにデータを保存します。絶対パスに変更することもできます:`-v /your/custom/path:/data`
142
+
143
+ </details>
144
+
145
+ ---
146
+
147
+ 🎉 デプロイが完了したら、`http://localhost:3000` にアクセスして使用を開始してください!
148
+
149
+ 📖 その他のデプロイ方法については[デプロイガイド](https://docs.newapi.pro/installation)を参照してください。
150
+
151
+ ---
152
+
153
+ ## 📚 ドキュメント
154
+
155
+ <div align="center">
156
+
157
+ ### 📖 [公式ドキュメント](https://docs.newapi.pro/) | [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/QuantumNous/new-api)
158
+
159
+ </div>
160
+
161
+ **クイックナビゲーション:**
162
+
163
+ | カテゴリ | リンク |
164
+ |------|------|
165
+ | 🚀 デプロイガイド | [インストールドキュメント](https://docs.newapi.pro/installation) |
166
+ | ⚙️ 環境設定 | [環境変数](https://docs.newapi.pro/installation/environment-variables) |
167
+ | 📡 APIドキュメント | [APIドキュメント](https://docs.newapi.pro/api) |
168
+ | ❓ よくある質問 | [FAQ](https://docs.newapi.pro/support/faq) |
169
+ | 💬 コミュニティ交流 | [交流チャネル](https://docs.newapi.pro/support/community-interaction) |
170
+
171
+ ---
172
+
173
+ ## ✨ 主な機能
174
+
175
+ > 詳細な機能については[機能説明](https://docs.newapi.pro/wiki/features-introduction)を参照してください。
176
+
177
+ ### 🎨 コア機能
178
+
179
+ | 機能 | 説明 |
180
+ |------|------|
181
+ | 🎨 新しいUI | モダンなユーザーインターフェースデザイン |
182
+ | 🌍 多言語 | 中国語、英語、フランス語、日本語をサポート |
183
+ | 🔄 データ互換性 | オリジナルのOne APIデータベースと完全に互換性あり |
184
+ | 📈 データダッシュボード | ビジュアルコンソールと統計分析 |
185
+ | 🔒 権限管理 | トークングループ化、モデル制限、ユーザー管理 |
186
+
187
+ ### 💰 支払いと課金
188
+
189
+ - ✅ オンライン充電(EPay、Stripe)
190
+ - ✅ モデルの従量課金
191
+ - ✅ キャッシュ課金サポート(OpenAI、Azure、DeepSeek、Claude、Qwenなどすべてのサポートされているモデル)
192
+ - ✅ 柔軟な課金ポリシー設定
193
+
194
+ ### 🔐 認証とセキュリティ
195
+
196
+ - 🤖 LinuxDO認証ログイン
197
+ - 📱 Telegram認証ログイン
198
+ - 🔑 OIDC統一認証
199
+
200
+
201
+
202
+ ### 🚀 高度な機能
203
+
204
+ **APIフォーマットサポート:**
205
+ - ⚡ [OpenAI Responses](https://docs.newapi.pro/api/openai-responses)
206
+ - ⚡ [OpenAI Realtime API](https://docs.newapi.pro/api/openai-realtime)(Azureを含む)
207
+ - ⚡ [Claude Messages](https://docs.newapi.pro/api/anthropic-chat)
208
+ - ⚡ [Google Gemini](https://docs.newapi.pro/api/google-gemini-chat/)
209
+ - 🔄 [Rerankモデル](https://docs.newapi.pro/api/jinaai-rerank)
210
+ - ⚡ [OpenAI Realtime API](https://docs.newapi.pro/api/openai-realtime)
211
+ - ⚡ [Claude Messages](https://docs.newapi.pro/api/anthropic-chat)
212
+ - ⚡ [Google Gemini](https://docs.newapi.pro/api/google-gemini-chat/)
213
+ - 🔄 [Rerankモデル](https://docs.newapi.pro/api/jinaai-rerank)(Cohere、Jina)
214
+
215
+ **インテリジェントルーティング:**
216
+ - ⚖️ チャネル重み付けランダム
217
+ - 🔄 失敗自動リトライ
218
+ - 🚦 ユーザーレベルモデルレート制限
219
+
220
+ **フォーマット変換:**
221
+ - 🔄 OpenAI ⇄ Claude Messages
222
+ - 🔄 OpenAI ⇄ Gemini Chat
223
+ - 🔄 思考からコンテンツへの機能
224
+
225
+ **Reasoning Effort サポート:**
226
+
227
+ <details>
228
+ <summary>詳細設定を表示</summary>
229
+
230
+ **OpenAIシリーズモデル:**
231
+ - `o3-mini-high` - 高思考努力
232
+ - `o3-mini-medium` - 中思考努力
233
+ - `o3-mini-low` - 低思考努力
234
+ - `gpt-5-high` - 高思考努力
235
+ - `gpt-5-medium` - 中思考努力
236
+ - `gpt-5-low` - 低思考努力
237
+
238
+ **Claude思考モデル:**
239
+ - `claude-3-7-sonnet-20250219-thinking` - 思考モードを有効にする
240
+
241
+ **Google Geminiシリーズモデル:**
242
+ - `gemini-2.5-flash-thinking` - 思考モードを有効にする
243
+ - `gemini-2.5-flash-nothinking` - 思考モードを無効にする
244
+ - `gemini-2.5-pro-thinking` - 思考モードを有効にする
245
+ - `gemini-2.5-pro-thinking-128` - 思考モードを有効にし、思考予算を128トークンに設定する
246
+
247
+ </details>
248
+
249
+ ---
250
+
251
+ ## 🤖 モデルサポート
252
+
253
+ > 詳細については[APIドキュメント - 中継インターフェース](https://docs.newapi.pro/api)
254
+
255
+ | モデルタイプ | 説明 | ドキュメント |
256
+ |---------|------|------|
257
+ | 🤖 OpenAI GPTs | gpt-4-gizmo-* シリーズ | - |
258
+ | 🎨 Midjourney-Proxy | [Midjourney-Proxy(Plus)](https://github.com/novicezk/midjourney-proxy) | [ドキュメント](https://docs.newapi.pro/api/midjourney-proxy-image) |
259
+ | 🎵 Suno-API | [Suno API](https://github.com/Suno-API/Suno-API) | [ドキュメント](https://docs.newapi.pro/api/suno-music) |
260
+ | 🔄 Rerank | Cohere、Jina | [ドキュメント](https://docs.newapi.pro/api/jinaai-rerank) |
261
+ | 💬 Claude | Messagesフォーマット | [ドキュメント](https://docs.newapi.pro/api/suno-music) |
262
+ | 🌐 Gemini | Google Geminiフォーマット | [ドキュメント](https://docs.newapi.pro/api/google-gemini-chat/) |
263
+ | 🔧 Dify | ChatFlowモード | - |
264
+ | 🎯 カスタム | 完全な呼び出しアドレスの入力をサポート | - |
265
+
266
+ ### 📡 サポートされているインターフェース
267
+
268
+ <details>
269
+ <summary>完全なインターフェースリストを表示</summary>
270
+
271
+ - [チャットインターフェース (Chat Completions)](https://docs.newapi.pro/api/openai-chat)
272
+ - [レスポンスインターフェース (Responses)](https://docs.newapi.pro/api/openai-responses)
273
+ - [イメージインターフェース (Image)](https://docs.newapi.pro/api/openai-image)
274
+ - [オーディオインターフェース (Audio)](https://docs.newapi.pro/api/openai-audio)
275
+ - [ビデオインターフェース (Video)](https://docs.newapi.pro/api/openai-video)
276
+ - [エンベッドインターフェース (Embeddings)](https://docs.newapi.pro/api/openai-embeddings)
277
+ - [再ランク付けインターフェース (Rerank)](https://docs.newapi.pro/api/jinaai-rerank)
278
+ - [リアルタイム対話インターフェース (Realtime)](https://docs.newapi.pro/api/openai-realtime)
279
+ - [Claudeチャット](https://docs.newapi.pro/api/anthropic-chat)
280
+ - [Google Geminiチャット](https://docs.newapi.pro/api/google-gemini-chat/)
281
+
282
+ </details>
283
+
284
+ ---
285
+
286
+ ## 🚢 デプロイ
287
+
288
+ > [!TIP]
289
+ > **最新のDockerイメージ:** `calciumion/new-api:latest`
290
+
291
+ ### 📋 デプロイ要件
292
+
293
+ | コンポーネント | 要件 |
294
+ |------|------|
295
+ | **ローカルデータベース** | SQLite(Dockerは `/data` ディレクトリをマウントする必要があります)|
296
+ | **リモートデータベース** | MySQL ≥ 5.7.8 または PostgreSQL ≥ 9.6 |
297
+ | **コンテナエンジン** | Docker / Docker Compose |
298
+
299
+ ### ⚙️ 環境変数設定
300
+
301
+ <details>
302
+ <summary>一般的な環境変数設定</summary>
303
+
304
+ | 変数名 | 説明 | デフォルト値 |
305
+ |--------|------|--------|
306
+ | `SESSION_SECRET` | セッションシークレット(マルチマシンデプロイに必須) | - |
307
+ | `CRYPTO_SECRET` | 暗号化シークレット(Redisに必須) | - |
308
+ | `SQL_DSN** | データベース接続文字列 | - |
309
+ | `REDIS_CONN_STRING` | Redis接続文字列 | - |
310
+ | `STREAMING_TIMEOUT` | ストリーミング応答のタイムアウト時間(秒) | `300` |
311
+ | `AZURE_DEFAULT_API_VERSION` | Azure APIバージョン | `2025-04-01-preview` |
312
+ | `ERROR_LOG_ENABLED` | エラーログスイッチ | `false` |
313
+
314
+ 📖 **完全な設定:** [環境変数ドキュメント](https://docs.newapi.pro/installation/environment-variables)
315
+
316
+ </details>
317
+
318
+ ### 🔧 デプロイ方法
319
+
320
+ <details>
321
+ <summary><strong>方法 1: Docker Compose(推奨)</strong></summary>
322
+
323
+ ```bash
324
+ # プロジェクトをクローン
325
+ git clone https://github.com/QuantumNous/new-api.git
326
+ cd new-api
327
+
328
+ # 設定を編集
329
+ nano docker-compose.yml
330
+
331
+ # サービスを起動
332
+ docker-compose up -d
333
+ ```
334
+
335
+ </details>
336
+
337
+ <details>
338
+ <summary><strong>方法 2: Dockerコマンド</strong></summary>
339
+
340
+ **SQLiteを使用:**
341
+ ```bash
342
+ docker run --name new-api -d --restart always \
343
+ -p 3000:3000 \
344
+ -e TZ=Asia/Shanghai \
345
+ -v ./data:/data \
346
+ calciumion/new-api:latest
347
+ ```
348
+
349
+ **MySQLを使用:**
350
+ ```bash
351
+ docker run --name new-api -d --restart always \
352
+ -p 3000:3000 \
353
+ -e SQL_DSN="root:123456@tcp(localhost:3306)/oneapi" \
354
+ -e TZ=Asia/Shanghai \
355
+ -v ./data:/data \
356
+ calciumion/new-api:latest
357
+ ```
358
+
359
+ > **💡 パス説明:**
360
+ > - `./data:/data` - 相対パス、データは現在のディレクトリのdataフォルダに保存されます
361
+ > - 絶対パスを使用することもできます:`/your/custom/path:/data`
362
+
363
+ </details>
364
+
365
+ <details>
366
+ <summary><strong>方法 3: 宝塔パネル</strong></summary>
367
+
368
+ 1. 宝塔パネル(**9.2.0バージョン**以上)をインストールし、アプリケーションストアで**New-API**を検索してインストールします。
369
+
370
+ 📖 [画像付きチュートリアル](./docs/BT.md)
371
+
372
+ </details>
373
+
374
+ ### ⚠️ マルチマシンデプロイの注意事項
375
+
376
+ > [!WARNING]
377
+ > - **必ず設定する必要があります** `SESSION_SECRET` - そうしないとマルチマシンデプロイ時にログイン状態が不一致になります
378
+ > - **共有Redisは必ず設定する必要があります** `CRYPTO_SECRET` - そうしないとデータを復号化できません
379
+
380
+ ### 🔄 チャネルリトライとキャッシュ
381
+
382
+ **リトライ設定:** `設定 → 運営設定 → 一般設定 → 失敗リトライ回数`
383
+
384
+ **キャッシュ設定:**
385
+ - `REDIS_CONN_STRING`:Redisキャッシュ(推奨)
386
+ - `MEMORY_CACHE_ENABLED`:メモリキャッシュ
387
+
388
+ ---
389
+
390
+ ## 🔗 関連プロジェクト
391
+
392
+ ### 上流プロジェクト
393
+
394
+ | プロジェクト | 説明 |
395
+ |------|------|
396
+ | [One API](https://github.com/songquanpeng/one-api) | オリジナルプロジェクトベース |
397
+ | [Midjourney-Proxy](https://github.com/novicezk/midjourney-proxy) | Midjourneyインターフェースサポート |
398
+
399
+ ### 補助ツール
400
+
401
+ | プロジェクト | 説明 |
402
+ |------|------|
403
+ | [neko-api-key-tool](https://github.com/Calcium-Ion/neko-api-key-tool) | キー使用量クォータ照会ツール |
404
+ | [new-api-horizon](https://github.com/Calcium-Ion/new-api-horizon) | New API高性能最適化版 |
405
+
406
+ ---
407
+
408
+ ## 💬 ヘルプサポート
409
+
410
+ ### 📖 ドキュメントリソース
411
+
412
+ | リソース | リンク |
413
+ |------|------|
414
+ | 📘 よくある質問 | [FAQ](https://docs.newapi.pro/support/faq) |
415
+ | 💬 コミュニティ交流 | [交流チャネル](https://docs.newapi.pro/support/community-interaction) |
416
+ | 🐛 問題のフィードバック | [問題フィードバック](https://docs.newapi.pro/support/feedback-issues) |
417
+ | 📚 完全なドキュメント | [公式ドキュメント](https://docs.newapi.pro/support) |
418
+
419
+ ### 🤝 貢献ガイド
420
+
421
+ あらゆる形の貢献を歓迎します!
422
+
423
+ - 🐛 バグを報告する
424
+ - 💡 新しい機能を提案する
425
+ - 📝 ドキュメントを改善する
426
+ - 🔧 コードを提出する
427
+
428
+ ---
429
+
430
+ ## 🌟 スター履歴
431
+
432
+ <div align="center">
433
+
434
+ [![スター履歴チャート](https://api.star-history.com/svg?repos=Calcium-Ion/new-api&type=Date)](https://star-history.com/#Calcium-Ion/new-api&Date)
435
+
436
+ </div>
437
+
438
+ ---
439
+
440
+ <div align="center">
441
+
442
+ ### 💖 New APIをご利用いただきありがとうございます
443
+
444
+ このプロジェクトがあなたのお役に立てたなら、ぜひ ⭐️ スターをください!
445
+
446
+ **[公式ドキュメント](https://docs.newapi.pro/)** • **[問題フィードバック](https://github.com/Calcium-Ion/new-api/issues)** • **[最新リリース](https://github.com/Calcium-Ion/new-api/releases)**
447
+
448
+ <sub>❤️ で構築された QuantumNous</sub>
449
+
450
+ </div>
README.md ADDED
@@ -0,0 +1,459 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: New API
3
+ emoji: 🚀
4
+ colorFrom: blue
5
+ colorTo: purple
6
+ sdk: docker
7
+ app_port: 7860
8
+ pinned: false
9
+ license: mit
10
+ ---
11
+
12
+ <div align="center">
13
+
14
+ ![new-api](/web/public/logo.png)
15
+
16
+ # New API
17
+
18
+ 🍥 **新一代大模型网关与AI资产管理系统**
19
+
20
+ <p align="center">
21
+ <strong>中文</strong> |
22
+ <a href="./README.en.md">English</a> |
23
+ <a href="./README.fr.md">Français</a> |
24
+ <a href="./README.ja.md">日本語</a>
25
+ </p>
26
+
27
+ <p align="center">
28
+ <a href="https://raw.githubusercontent.com/Calcium-Ion/new-api/main/LICENSE">
29
+ <img src="https://img.shields.io/github/license/Calcium-Ion/new-api?color=brightgreen" alt="license">
30
+ </a>
31
+ <a href="https://github.com/Calcium-Ion/new-api/releases/latest">
32
+ <img src="https://img.shields.io/github/v/release/Calcium-Ion/new-api?color=brightgreen&include_prereleases" alt="release">
33
+ </a>
34
+ <a href="https://github.com/users/Calcium-Ion/packages/container/package/new-api">
35
+ <img src="https://img.shields.io/badge/docker-ghcr.io-blue" alt="docker">
36
+ </a>
37
+ <a href="https://hub.docker.com/r/CalciumIon/new-api">
38
+ <img src="https://img.shields.io/badge/docker-dockerHub-blue" alt="docker">
39
+ </a>
40
+ <a href="https://goreportcard.com/report/github.com/Calcium-Ion/new-api">
41
+ <img src="https://goreportcard.com/badge/github.com/Calcium-Ion/new-api" alt="GoReportCard">
42
+ </a>
43
+ </p>
44
+
45
+ <p align="center">
46
+ <a href="https://trendshift.io/repositories/8227" target="_blank">
47
+ <img src="https://trendshift.io/api/badge/repositories/8227" alt="Calcium-Ion%2Fnew-api | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/>
48
+ </a>
49
+ </p>
50
+
51
+ <p align="center">
52
+ <a href="#-快速开始">快速开始</a> •
53
+ <a href="#-主要特性">主要特性</a> •
54
+ <a href="#-部署">部署</a> •
55
+ <a href="#-文档">文档</a> •
56
+ <a href="#-帮助支持">帮助</a>
57
+ </p>
58
+
59
+ </div>
60
+
61
+ ## 📝 项目说明
62
+
63
+ > [!NOTE]
64
+ > 本项目为开源项目,在 [One API](https://github.com/songquanpeng/one-api) 的基础上进行二次开发
65
+
66
+ > [!IMPORTANT]
67
+ > - 本项目仅供个人学习使用,不保证稳定性,且不提供任何技术支持
68
+ > - 使用者必须在遵循 OpenAI 的 [使用条款](https://openai.com/policies/terms-of-use) 以及**法律法规**的情况下使用,不得用于非法用途
69
+ > - 根据 [《生成式人工智能服务管理暂行办法》](http://www.cac.gov.cn/2023-07/13/c_1690898327029107.htm) 的要求,请勿对中国地区公众提供一切未经备案的生成式人工智能服务
70
+
71
+ ---
72
+
73
+ ## 🤝 我们信任的合作伙伴
74
+
75
+ <p align="center">
76
+ <em>排名不分先后</em>
77
+ </p>
78
+
79
+ <p align="center">
80
+ <a href="https://www.cherry-ai.com/" target="_blank">
81
+ <img src="./docs/images/cherry-studio.png" alt="Cherry Studio" height="80" />
82
+ </a>
83
+ <a href="https://bda.pku.edu.cn/" target="_blank">
84
+ <img src="./docs/images/pku.png" alt="北京大学" height="80" />
85
+ </a>
86
+ <a href="https://www.compshare.cn/?ytag=GPU_yy_gh_newapi" target="_blank">
87
+ <img src="./docs/images/ucloud.png" alt="UCloud 优刻得" height="80" />
88
+ </a>
89
+ <a href="https://www.aliyun.com/" target="_blank">
90
+ <img src="./docs/images/aliyun.png" alt="阿里云" height="80" />
91
+ </a>
92
+ <a href="https://io.net/" target="_blank">
93
+ <img src="./docs/images/io-net.png" alt="IO.NET" height="80" />
94
+ </a>
95
+ </p>
96
+
97
+ ---
98
+
99
+ ## 🙏 特别鸣谢
100
+
101
+ <p align="center">
102
+ <a href="https://www.jetbrains.com/?from=new-api" target="_blank">
103
+ <img src="https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.png" alt="JetBrains Logo" width="120" />
104
+ </a>
105
+ </p>
106
+
107
+ <p align="center">
108
+ <strong>感谢 <a href="https://www.jetbrains.com/?from=new-api">JetBrains</a> 为本项目提供免费的开源开发许可证</strong>
109
+ </p>
110
+
111
+ ---
112
+
113
+ ## 🚀 快速开始
114
+
115
+ ### 使用 Docker Compose(推荐)
116
+
117
+ ```bash
118
+ # 克隆项目
119
+ git clone https://github.com/QuantumNous/new-api.git
120
+ cd new-api
121
+
122
+ # 编辑 docker-compose.yml 配置
123
+ nano docker-compose.yml
124
+
125
+ # 启动服务
126
+ docker-compose up -d
127
+ ```
128
+
129
+ <details>
130
+ <summary><strong>使用 Docker 命令</strong></summary>
131
+
132
+ ```bash
133
+ # 拉取最新镜像
134
+ docker pull calciumion/new-api:latest
135
+
136
+ # 使用 SQLite(默认)
137
+ docker run --name new-api -d --restart always \
138
+ -p 3000:3000 \
139
+ -e TZ=Asia/Shanghai \
140
+ -v ./data:/data \
141
+ calciumion/new-api:latest
142
+
143
+ # 使用 MySQL
144
+ docker run --name new-api -d --restart always \
145
+ -p 3000:3000 \
146
+ -e SQL_DSN="root:123456@tcp(localhost:3306)/oneapi" \
147
+ -e TZ=Asia/Shanghai \
148
+ -v ./data:/data \
149
+ calciumion/new-api:latest
150
+ ```
151
+
152
+ > **💡 提示:** `-v ./data:/data` 会将数据保存在当前目录的 `data` 文件夹中,你也可以改为绝对路径如 `-v /your/custom/path:/data`
153
+
154
+ </details>
155
+
156
+ ---
157
+
158
+ 🎉 部署完成后,访问 `http://localhost:3000` 即可使用!
159
+
160
+ 📖 更多部署方式请参考 [部署指南](https://docs.newapi.pro/installation)
161
+
162
+ ---
163
+
164
+ ## 📚 文档
165
+
166
+ <div align="center">
167
+
168
+ ### 📖 [官方文档](https://docs.newapi.pro/) | [![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/QuantumNous/new-api)
169
+
170
+ </div>
171
+
172
+ **快速导航:**
173
+
174
+ | 分类 | 链接 |
175
+ |------|------|
176
+ | 🚀 部署指南 | [安装文档](https://docs.newapi.pro/installation) |
177
+ | ⚙️ 环境配置 | [环境变量](https://docs.newapi.pro/installation/environment-variables) |
178
+ | 📡 接口文档 | [API 文档](https://docs.newapi.pro/api) |
179
+ | ❓ 常见问题 | [FAQ](https://docs.newapi.pro/support/faq) |
180
+ | 💬 社区交流 | [交流渠道](https://docs.newapi.pro/support/community-interaction) |
181
+
182
+ ---
183
+
184
+ ## ✨ 主要特性
185
+
186
+ > 详细特性请参考 [特性说明](https://docs.newapi.pro/wiki/features-introduction)
187
+
188
+ ### 🎨 核心功能
189
+
190
+ | 特性 | 说明 |
191
+ |------|------|
192
+ | 🎨 全新 UI | 现代化的用户界面设计 |
193
+ | 🌍 多语言 | 支持中文、英文、法语、日语 |
194
+ | 🔄 数据兼容 | 完全兼容原版 One API 数据库 |
195
+ | 📈 数据看板 | 可视化控制台与统计分析 |
196
+ | 🔒 权限管理 | 令牌分组、模型限制、用户管理 |
197
+
198
+ ### 💰 支付与计费
199
+
200
+ - ✅ 在线充值(易支付、Stripe)
201
+ - ✅ 模型按次数收费
202
+ - ✅ 缓存计费支持(OpenAI、Azure、DeepSeek、Claude、Qwen等所有支持的模型)
203
+ - ✅ 灵活的计费策略配置
204
+
205
+ ### 🔐 授权与安全
206
+
207
+ - 😈 Discord 授权登录
208
+ - 🤖 LinuxDO 授权登录
209
+ - 📱 Telegram 授权登录
210
+ - 🔑 OIDC 统一认证
211
+ - 🔍 Key 查询使用额度(配合 [neko-api-key-tool](https://github.com/Calcium-Ion/neko-api-key-tool))
212
+
213
+ ### 🚀 高级功能
214
+
215
+ **API 格式支持:**
216
+ - ⚡ [OpenAI Responses](https://docs.newapi.pro/api/openai-responses)
217
+ - ⚡ [OpenAI Realtime API](https://docs.newapi.pro/api/openai-realtime)(含 Azure)
218
+ - ⚡ [Claude Messages](https://docs.newapi.pro/api/anthropic-chat)
219
+ - ⚡ [Google Gemini](https://docs.newapi.pro/api/google-gemini-chat/)
220
+ - 🔄 [Rerank 模型](https://docs.newapi.pro/api/jinaai-rerank)(Cohere、Jina)
221
+
222
+ **智能路由:**
223
+ - ⚖️ 渠道加权随机
224
+ - 🔄 失败自动重试
225
+ - 🚦 用户级别模型限流
226
+
227
+ **格式转换:**
228
+ - 🔄 OpenAI ⇄ Claude Messages
229
+ - 🔄 OpenAI ⇄ Gemini Chat
230
+ - 🔄 思考转内容功能
231
+
232
+ **Reasoning Effort 支持:**
233
+
234
+ <details>
235
+ <summary>查看详细配置</summary>
236
+
237
+ **OpenAI 系列模型:**
238
+ - `o3-mini-high` - High reasoning effort
239
+ - `o3-mini-medium` - Medium reasoning effort
240
+ - `o3-mini-low` - Low reasoning effort
241
+ - `gpt-5-high` - High reasoning effort
242
+ - `gpt-5-medium` - Medium reasoning effort
243
+ - `gpt-5-low` - Low reasoning effort
244
+
245
+ **Claude 思考模型:**
246
+ - `claude-3-7-sonnet-20250219-thinking` - 启用思考模式
247
+
248
+ **Google Gemini 系列模型:**
249
+ - `gemini-2.5-flash-thinking` - 启用思考模式
250
+ - `gemini-2.5-flash-nothinking` - 禁用思考模式
251
+ - `gemini-2.5-pro-thinking` - 启用思考模式
252
+ - `gemini-2.5-pro-thinking-128` - 启用思考模式,并设置思考预算为128tokens
253
+
254
+ </details>
255
+
256
+ ---
257
+
258
+ ## 🤖 模型支持
259
+
260
+ > 详情请参考 [接口文档 - 中继接口](https://docs.newapi.pro/api)
261
+
262
+ | 模型类型 | 说明 | 文档 |
263
+ |---------|------|------|
264
+ | 🤖 OpenAI GPTs | gpt-4-gizmo-* 系列 | - |
265
+ | 🎨 Midjourney-Proxy | [Midjourney-Proxy(Plus)](https://github.com/novicezk/midjourney-proxy) | [文档](https://docs.newapi.pro/api/midjourney-proxy-image) |
266
+ | 🎵 Suno-API | [Suno API](https://github.com/Suno-API/Suno-API) | [文档](https://docs.newapi.pro/api/suno-music) |
267
+ | 🔄 Rerank | Cohere、Jina | [文档](https://docs.newapi.pro/api/jinaai-rerank) |
268
+ | 💬 Claude | Messages 格式 | [文档](https://docs.newapi.pro/api/anthropic-chat) |
269
+ | 🌐 Gemini | Google Gemini 格式 | [文档](https://docs.newapi.pro/api/google-gemini-chat/) |
270
+ | 🔧 Dify | ChatFlow 模式 | - |
271
+ | 🎯 自定义 | 支持完整调用地址 | - |
272
+
273
+ ### 📡 支持的接口
274
+
275
+ <details>
276
+ <summary>查看完整接口列表</summary>
277
+
278
+ - [聊天接口 (Chat Completions)](https://docs.newapi.pro/api/openai-chat)
279
+ - [响应接口 (Responses)](https://docs.newapi.pro/api/openai-responses)
280
+ - [图像接口 (Image)](https://docs.newapi.pro/api/openai-image)
281
+ - [音频接口 (Audio)](https://docs.newapi.pro/api/openai-audio)
282
+ - [视频接口 (Video)](https://docs.newapi.pro/api/openai-video)
283
+ - [嵌入接口 (Embeddings)](https://docs.newapi.pro/api/openai-embeddings)
284
+ - [重排序接口 (Rerank)](https://docs.newapi.pro/api/jinaai-rerank)
285
+ - [实时对话 (Realtime)](https://docs.newapi.pro/api/openai-realtime)
286
+ - [Claude 聊天](https://docs.newapi.pro/api/anthropic-chat)
287
+ - [Google Gemini 聊天](https://docs.newapi.pro/api/google-gemini-chat)
288
+
289
+ </details>
290
+
291
+ ---
292
+
293
+ ## 🚢 部署
294
+
295
+ > [!TIP]
296
+ > **最新版 Docker 镜像:** `calciumion/new-api:latest`
297
+
298
+ ### 📋 部署要求
299
+
300
+ | 组件 | 要求 |
301
+ |------|------|
302
+ | **本地数据库** | SQLite(Docker 需挂载 `/data` 目录)|
303
+ | **远程数据库** | MySQL ≥ 5.7.8 或 PostgreSQL ≥ 9.6 |
304
+ | **容器引擎** | Docker / Docker Compose |
305
+
306
+ ### ⚙️ 环境变量配置
307
+
308
+ <details>
309
+ <summary>常用环境变量配置</summary>
310
+
311
+ | 变量名 | 说明 | 默认值 |
312
+ |--------|------|--------|
313
+ | `SESSION_SECRET` | 会话密钥(多机部署必须) | - |
314
+ | `CRYPTO_SECRET` | 加密密钥(Redis 必须) | - |
315
+ | `SQL_DSN` | 数据库连接字符串 | - |
316
+ | `REDIS_CONN_STRING` | Redis 连接字符串 | - |
317
+ | `STREAMING_TIMEOUT` | 流式超时时间(秒) | `300` |
318
+ | `AZURE_DEFAULT_API_VERSION` | Azure API 版本 | `2025-04-01-preview` |
319
+ | `ERROR_LOG_ENABLED` | 错误日志开关 | `false` |
320
+
321
+ 📖 **完整配置:** [环境变量文档](https://docs.newapi.pro/installation/environment-variables)
322
+
323
+ </details>
324
+
325
+ ### 🔧 部署方式
326
+
327
+ <details>
328
+ <summary><strong>方式 1:Docker Compose(推荐)</strong></summary>
329
+
330
+ ```bash
331
+ # 克隆项目
332
+ git clone https://github.com/QuantumNous/new-api.git
333
+ cd new-api
334
+
335
+ # 编辑配置
336
+ nano docker-compose.yml
337
+
338
+ # 启动服务
339
+ docker-compose up -d
340
+ ```
341
+
342
+ </details>
343
+
344
+ <details>
345
+ <summary><strong>方式 2:Docker 命令</strong></summary>
346
+
347
+ **使用 SQLite:**
348
+ ```bash
349
+ docker run --name new-api -d --restart always \
350
+ -p 3000:3000 \
351
+ -e TZ=Asia/Shanghai \
352
+ -v ./data:/data \
353
+ calciumion/new-api:latest
354
+ ```
355
+
356
+ **使用 MySQL:**
357
+ ```bash
358
+ docker run --name new-api -d --restart always \
359
+ -p 3000:3000 \
360
+ -e SQL_DSN="root:123456@tcp(localhost:3306)/oneapi" \
361
+ -e TZ=Asia/Shanghai \
362
+ -v ./data:/data \
363
+ calciumion/new-api:latest
364
+ ```
365
+
366
+ > **💡 路径说明:**
367
+ > - `./data:/data` - 相对路径,数据保存在当前目录的 data 文件夹
368
+ > - 也可使用绝对路径,如:`/your/custom/path:/data`
369
+
370
+ </details>
371
+
372
+ <details>
373
+ <summary><strong>方式 3:宝塔面板</strong></summary>
374
+
375
+ 1. 安装宝塔面板(≥ 9.2.0 版本)
376
+ 2. 在应用商店搜索 **New-API**
377
+ 3. 一键安装
378
+
379
+ 📖 [图文教程](./docs/BT.md)
380
+
381
+ </details>
382
+
383
+ ### ⚠️ 多机部署注意事项
384
+
385
+ > [!WARNING]
386
+ > - **必须设置** `SESSION_SECRET` - 否则登录状态不一致
387
+ > - **公用 Redis 必须设置** `CRYPTO_SECRET` - 否则数据无法解密
388
+
389
+ ### 🔄 渠道重试与缓存
390
+
391
+ **重试配置:** `设置 → 运营设置 → 通用设置 → 失败重试次数`
392
+
393
+ **缓存配置:**
394
+ - `REDIS_CONN_STRING`:Redis 缓存(推荐)
395
+ - `MEMORY_CACHE_ENABLED`:内存缓存
396
+
397
+ ---
398
+
399
+ ## 🔗 相关项目
400
+
401
+ ### 上游项目
402
+
403
+ | 项目 | 说明 |
404
+ |------|------|
405
+ | [One API](https://github.com/songquanpeng/one-api) | 原版项目基础 |
406
+ | [Midjourney-Proxy](https://github.com/novicezk/midjourney-proxy) | Midjourney 接口支持 |
407
+
408
+ ### 配套工具
409
+
410
+ | 项目 | 说明 |
411
+ |------|------|
412
+ | [neko-api-key-tool](https://github.com/Calcium-Ion/neko-api-key-tool) | Key 额度查询工具 |
413
+ | [new-api-horizon](https://github.com/Calcium-Ion/new-api-horizon) | New API 高性能优化版 |
414
+
415
+ ---
416
+
417
+ ## 💬 帮助支持
418
+
419
+ ### 📖 文档资源
420
+
421
+ | 资源 | 链接 |
422
+ |------|------|
423
+ | 📘 常见问题 | [FAQ](https://docs.newapi.pro/support/faq) |
424
+ | 💬 社区交流 | [交流渠道](https://docs.newapi.pro/support/community-interaction) |
425
+ | 🐛 反馈问题 | [问题反馈](https://docs.newapi.pro/support/feedback-issues) |
426
+ | 📚 完整文档 | [官方文档](https://docs.newapi.pro/support) |
427
+
428
+ ### 🤝 贡献指南
429
+
430
+ 欢迎各种形式的贡献!
431
+
432
+ - 🐛 报告 Bug
433
+ - 💡 提出新功能
434
+ - 📝 改进文档
435
+ - 🔧 提交代码
436
+
437
+ ---
438
+
439
+ ## 🌟 Star History
440
+
441
+ <div align="center">
442
+
443
+ [![Star History Chart](https://api.star-history.com/svg?repos=Calcium-Ion/new-api&type=Date)](https://star-history.com/#Calcium-Ion/new-api&Date)
444
+
445
+ </div>
446
+
447
+ ---
448
+
449
+ <div align="center">
450
+
451
+ ### 💖 感谢使用 New API
452
+
453
+ 如果这个项目对你有帮助,欢迎给我们一个 ⭐️ Star!
454
+
455
+ **[官方文档](https://docs.newapi.pro/)** • **[问题反馈](https://github.com/Calcium-Ion/new-api/issues)** • **[最新发布](https://github.com/Calcium-Ion/new-api/releases)**
456
+
457
+ <sub>Built with ❤️ by QuantumNous</sub>
458
+
459
+ </div>
VERSION ADDED
File without changes
bin/migration_v0.2-v0.3.sql ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ UPDATE users
2
+ SET quota = quota + (
3
+ SELECT SUM(remain_quota)
4
+ FROM tokens
5
+ WHERE tokens.user_id = users.id
6
+ )
bin/migration_v0.3-v0.4.sql ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ INSERT INTO abilities (`group`, model, channel_id, enabled)
2
+ SELECT c.`group`, m.model, c.id, 1
3
+ FROM channels c
4
+ CROSS JOIN (
5
+ SELECT 'gpt-3.5-turbo' AS model UNION ALL
6
+ SELECT 'gpt-3.5-turbo-0301' AS model UNION ALL
7
+ SELECT 'gpt-4' AS model UNION ALL
8
+ SELECT 'gpt-4-0314' AS model
9
+ ) AS m
10
+ WHERE c.status = 1
11
+ AND NOT EXISTS (
12
+ SELECT 1
13
+ FROM abilities a
14
+ WHERE a.`group` = c.`group`
15
+ AND a.model = m.model
16
+ AND a.channel_id = c.id
17
+ );
bin/time_test.sh ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+
3
+ if [ $# -lt 3 ]; then
4
+ echo "Usage: time_test.sh <domain> <key> <count> [<model>]"
5
+ exit 1
6
+ fi
7
+
8
+ domain=$1
9
+ key=$2
10
+ count=$3
11
+ model=${4:-"gpt-3.5-turbo"} # 设置默认模型为 gpt-3.5-turbo
12
+
13
+ total_time=0
14
+ times=()
15
+
16
+ for ((i=1; i<=count; i++)); do
17
+ result=$(curl -o /dev/null -s -w "%{http_code} %{time_total}\\n" \
18
+ https://"$domain"/v1/chat/completions \
19
+ -H "Content-Type: application/json" \
20
+ -H "Authorization: Bearer $key" \
21
+ -d '{"messages": [{"content": "echo hi", "role": "user"}], "model": "'"$model"'", "stream": false, "max_tokens": 1}')
22
+ http_code=$(echo "$result" | awk '{print $1}')
23
+ time=$(echo "$result" | awk '{print $2}')
24
+ echo "HTTP status code: $http_code, Time taken: $time"
25
+ total_time=$(bc <<< "$total_time + $time")
26
+ times+=("$time")
27
+ done
28
+
29
+ average_time=$(echo "scale=4; $total_time / $count" | bc)
30
+
31
+ sum_of_squares=0
32
+ for time in "${times[@]}"; do
33
+ difference=$(echo "scale=4; $time - $average_time" | bc)
34
+ square=$(echo "scale=4; $difference * $difference" | bc)
35
+ sum_of_squares=$(echo "scale=4; $sum_of_squares + $square" | bc)
36
+ done
37
+
38
+ standard_deviation=$(echo "scale=4; sqrt($sum_of_squares / $count)" | bc)
39
+
40
+ echo "Average time: $average_time±$standard_deviation"
common/api_type.go ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package common
2
+
3
+ import "github.com/QuantumNous/new-api/constant"
4
+
5
+ func ChannelType2APIType(channelType int) (int, bool) {
6
+ apiType := -1
7
+ switch channelType {
8
+ case constant.ChannelTypeOpenAI:
9
+ apiType = constant.APITypeOpenAI
10
+ case constant.ChannelTypeAnthropic:
11
+ apiType = constant.APITypeAnthropic
12
+ case constant.ChannelTypeBaidu:
13
+ apiType = constant.APITypeBaidu
14
+ case constant.ChannelTypePaLM:
15
+ apiType = constant.APITypePaLM
16
+ case constant.ChannelTypeZhipu:
17
+ apiType = constant.APITypeZhipu
18
+ case constant.ChannelTypeAli:
19
+ apiType = constant.APITypeAli
20
+ case constant.ChannelTypeXunfei:
21
+ apiType = constant.APITypeXunfei
22
+ case constant.ChannelTypeAIProxyLibrary:
23
+ apiType = constant.APITypeAIProxyLibrary
24
+ case constant.ChannelTypeTencent:
25
+ apiType = constant.APITypeTencent
26
+ case constant.ChannelTypeGemini:
27
+ apiType = constant.APITypeGemini
28
+ case constant.ChannelTypeZhipu_v4:
29
+ apiType = constant.APITypeZhipuV4
30
+ case constant.ChannelTypeOllama:
31
+ apiType = constant.APITypeOllama
32
+ case constant.ChannelTypePerplexity:
33
+ apiType = constant.APITypePerplexity
34
+ case constant.ChannelTypeAws:
35
+ apiType = constant.APITypeAws
36
+ case constant.ChannelTypeCohere:
37
+ apiType = constant.APITypeCohere
38
+ case constant.ChannelTypeDify:
39
+ apiType = constant.APITypeDify
40
+ case constant.ChannelTypeJina:
41
+ apiType = constant.APITypeJina
42
+ case constant.ChannelCloudflare:
43
+ apiType = constant.APITypeCloudflare
44
+ case constant.ChannelTypeSiliconFlow:
45
+ apiType = constant.APITypeSiliconFlow
46
+ case constant.ChannelTypeVertexAi:
47
+ apiType = constant.APITypeVertexAi
48
+ case constant.ChannelTypeMistral:
49
+ apiType = constant.APITypeMistral
50
+ case constant.ChannelTypeDeepSeek:
51
+ apiType = constant.APITypeDeepSeek
52
+ case constant.ChannelTypeMokaAI:
53
+ apiType = constant.APITypeMokaAI
54
+ case constant.ChannelTypeVolcEngine:
55
+ apiType = constant.APITypeVolcEngine
56
+ case constant.ChannelTypeBaiduV2:
57
+ apiType = constant.APITypeBaiduV2
58
+ case constant.ChannelTypeOpenRouter:
59
+ apiType = constant.APITypeOpenRouter
60
+ case constant.ChannelTypeXinference:
61
+ apiType = constant.APITypeXinference
62
+ case constant.ChannelTypeXai:
63
+ apiType = constant.APITypeXai
64
+ case constant.ChannelTypeCoze:
65
+ apiType = constant.APITypeCoze
66
+ case constant.ChannelTypeJimeng:
67
+ apiType = constant.APITypeJimeng
68
+ case constant.ChannelTypeMoonshot:
69
+ apiType = constant.APITypeMoonshot
70
+ case constant.ChannelTypeSubmodel:
71
+ apiType = constant.APITypeSubmodel
72
+ case constant.ChannelTypeMiniMax:
73
+ apiType = constant.APITypeMiniMax
74
+ case constant.ChannelTypeReplicate:
75
+ apiType = constant.APITypeReplicate
76
+ }
77
+ if apiType == -1 {
78
+ return constant.APITypeOpenAI, false
79
+ }
80
+ return apiType, true
81
+ }
common/audio.go ADDED
@@ -0,0 +1,296 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package common
2
+
3
+ import (
4
+ "context"
5
+ "encoding/binary"
6
+ "fmt"
7
+ "io"
8
+
9
+ "github.com/abema/go-mp4"
10
+ "github.com/go-audio/aiff"
11
+ "github.com/go-audio/wav"
12
+ "github.com/jfreymuth/oggvorbis"
13
+ "github.com/mewkiz/flac"
14
+ "github.com/pkg/errors"
15
+ "github.com/tcolgate/mp3"
16
+ "github.com/yapingcat/gomedia/go-codec"
17
+ )
18
+
19
+ // GetAudioDuration 使用纯 Go 库获取音频文件的时长(秒)。
20
+ // 它不再依赖外部的 ffmpeg 或 ffprobe 程序。
21
+ func GetAudioDuration(ctx context.Context, f io.ReadSeeker, ext string) (duration float64, err error) {
22
+ SysLog(fmt.Sprintf("GetAudioDuration: ext=%s", ext))
23
+ // 根据文件扩展名选择解析器
24
+ switch ext {
25
+ case ".mp3":
26
+ duration, err = getMP3Duration(f)
27
+ case ".wav":
28
+ duration, err = getWAVDuration(f)
29
+ case ".flac":
30
+ duration, err = getFLACDuration(f)
31
+ case ".m4a", ".mp4":
32
+ duration, err = getM4ADuration(f)
33
+ case ".ogg", ".oga", ".opus":
34
+ duration, err = getOGGDuration(f)
35
+ if err != nil {
36
+ duration, err = getOpusDuration(f)
37
+ }
38
+ case ".aiff", ".aif", ".aifc":
39
+ duration, err = getAIFFDuration(f)
40
+ case ".webm":
41
+ duration, err = getWebMDuration(f)
42
+ case ".aac":
43
+ duration, err = getAACDuration(f)
44
+ default:
45
+ return 0, fmt.Errorf("unsupported audio format: %s", ext)
46
+ }
47
+ SysLog(fmt.Sprintf("GetAudioDuration: duration=%f", duration))
48
+ return duration, err
49
+ }
50
+
51
+ // getMP3Duration 解析 MP3 文件以获取时长。
52
+ // 注意:对于 VBR (Variable Bitrate) MP3,这个估算可能不完全精确,但通常足够好。
53
+ // FFmpeg 在这种情况下会扫描整个文件来获得精确值,但这里的库提供了快速估算。
54
+ func getMP3Duration(r io.Reader) (float64, error) {
55
+ d := mp3.NewDecoder(r)
56
+ var f mp3.Frame
57
+ skipped := 0
58
+ duration := 0.0
59
+
60
+ for {
61
+ if err := d.Decode(&f, &skipped); err != nil {
62
+ if err == io.EOF {
63
+ break
64
+ }
65
+ return 0, errors.Wrap(err, "failed to decode mp3 frame")
66
+ }
67
+ duration += f.Duration().Seconds()
68
+ }
69
+ return duration, nil
70
+ }
71
+
72
+ // getWAVDuration 解析 WAV 文件头以获取时长。
73
+ func getWAVDuration(r io.ReadSeeker) (float64, error) {
74
+ dec := wav.NewDecoder(r)
75
+ if !dec.IsValidFile() {
76
+ return 0, errors.New("invalid wav file")
77
+ }
78
+ d, err := dec.Duration()
79
+ if err != nil {
80
+ return 0, errors.Wrap(err, "failed to get wav duration")
81
+ }
82
+ return d.Seconds(), nil
83
+ }
84
+
85
+ // getFLACDuration 解析 FLAC 文件的 STREAMINFO 块。
86
+ func getFLACDuration(r io.Reader) (float64, error) {
87
+ stream, err := flac.Parse(r)
88
+ if err != nil {
89
+ return 0, errors.Wrap(err, "failed to parse flac stream")
90
+ }
91
+ defer stream.Close()
92
+
93
+ // 时长 = 总采样数 / 采样率
94
+ duration := float64(stream.Info.NSamples) / float64(stream.Info.SampleRate)
95
+ return duration, nil
96
+ }
97
+
98
+ // getM4ADuration 解析 M4A/MP4 文件的 'mvhd' box。
99
+ func getM4ADuration(r io.ReadSeeker) (float64, error) {
100
+ // go-mp4 库需要 ReadSeeker 接口
101
+ info, err := mp4.Probe(r)
102
+ if err != nil {
103
+ return 0, errors.Wrap(err, "failed to probe m4a/mp4 file")
104
+ }
105
+ // 时长 = Duration / Timescale
106
+ return float64(info.Duration) / float64(info.Timescale), nil
107
+ }
108
+
109
+ // getOGGDuration 解析 OGG/Vorbis 文件以获取时长。
110
+ func getOGGDuration(r io.ReadSeeker) (float64, error) {
111
+ // 重置 reader 到开头
112
+ if _, err := r.Seek(0, io.SeekStart); err != nil {
113
+ return 0, errors.Wrap(err, "failed to seek ogg file")
114
+ }
115
+
116
+ reader, err := oggvorbis.NewReader(r)
117
+ if err != nil {
118
+ return 0, errors.Wrap(err, "failed to create ogg vorbis reader")
119
+ }
120
+
121
+ // 计算时长 = 总采样数 / 采样率
122
+ // 需要读取整个文件来获取总采样数
123
+ channels := reader.Channels()
124
+ sampleRate := reader.SampleRate()
125
+
126
+ // 估算方法:读取到文件结尾
127
+ var totalSamples int64
128
+ buf := make([]float32, 4096*channels)
129
+ for {
130
+ n, err := reader.Read(buf)
131
+ if err == io.EOF {
132
+ break
133
+ }
134
+ if err != nil {
135
+ return 0, errors.Wrap(err, "failed to read ogg samples")
136
+ }
137
+ totalSamples += int64(n / channels)
138
+ }
139
+
140
+ duration := float64(totalSamples) / float64(sampleRate)
141
+ return duration, nil
142
+ }
143
+
144
+ // getOpusDuration 解析 Opus 文件(在 OGG 容器中)以获取时长。
145
+ func getOpusDuration(r io.ReadSeeker) (float64, error) {
146
+ // Opus 通常封装在 OGG 容器中
147
+ // 我们需要解析 OGG 页面来获取时长信息
148
+ if _, err := r.Seek(0, io.SeekStart); err != nil {
149
+ return 0, errors.Wrap(err, "failed to seek opus file")
150
+ }
151
+
152
+ // 读取 OGG 页面头部
153
+ var totalGranulePos int64
154
+ buf := make([]byte, 27) // OGG 页面头部最小大小
155
+
156
+ for {
157
+ n, err := r.Read(buf)
158
+ if err == io.EOF {
159
+ break
160
+ }
161
+ if err != nil {
162
+ return 0, errors.Wrap(err, "failed to read opus/ogg page")
163
+ }
164
+ if n < 27 {
165
+ break
166
+ }
167
+
168
+ // 检查 OGG 页面标识 "OggS"
169
+ if string(buf[0:4]) != "OggS" {
170
+ // 跳过一些字节继续寻找
171
+ if _, err := r.Seek(-26, io.SeekCurrent); err != nil {
172
+ break
173
+ }
174
+ continue
175
+ }
176
+
177
+ // 读取 granule position (字节 6-13, 小端序)
178
+ granulePos := int64(binary.LittleEndian.Uint64(buf[6:14]))
179
+ if granulePos > totalGranulePos {
180
+ totalGranulePos = granulePos
181
+ }
182
+
183
+ // 读取段表大小
184
+ numSegments := int(buf[26])
185
+ segmentTable := make([]byte, numSegments)
186
+ if _, err := io.ReadFull(r, segmentTable); err != nil {
187
+ break
188
+ }
189
+
190
+ // 计算页面数据大小并跳过
191
+ var pageSize int
192
+ for _, segSize := range segmentTable {
193
+ pageSize += int(segSize)
194
+ }
195
+ if _, err := r.Seek(int64(pageSize), io.SeekCurrent); err != nil {
196
+ break
197
+ }
198
+ }
199
+
200
+ // Opus 的采样率固定为 48000 Hz
201
+ duration := float64(totalGranulePos) / 48000.0
202
+ return duration, nil
203
+ }
204
+
205
+ // getAIFFDuration 解析 AIFF 文件头以获取时长。
206
+ func getAIFFDuration(r io.ReadSeeker) (float64, error) {
207
+ if _, err := r.Seek(0, io.SeekStart); err != nil {
208
+ return 0, errors.Wrap(err, "failed to seek aiff file")
209
+ }
210
+
211
+ dec := aiff.NewDecoder(r)
212
+ if !dec.IsValidFile() {
213
+ return 0, errors.New("invalid aiff file")
214
+ }
215
+
216
+ d, err := dec.Duration()
217
+ if err != nil {
218
+ return 0, errors.Wrap(err, "failed to get aiff duration")
219
+ }
220
+
221
+ return d.Seconds(), nil
222
+ }
223
+
224
+ // getWebMDuration 解析 WebM 文件以获取时长。
225
+ // WebM 使用 Matroska 容器格式
226
+ func getWebMDuration(r io.ReadSeeker) (float64, error) {
227
+ if _, err := r.Seek(0, io.SeekStart); err != nil {
228
+ return 0, errors.Wrap(err, "failed to seek webm file")
229
+ }
230
+
231
+ // WebM/Matroska 文件的解析比较复杂
232
+ // 这里提供一个简化的实现,读取 EBML 头部
233
+ // 对于完整的 WebM 解析,可能需要使用专门的库
234
+
235
+ // 简单实现:查找 Duration 元素
236
+ // WebM Duration 的 Element ID 是 0x4489
237
+ // 这是一个简化版本,可能不适用于所有 WebM 文件
238
+ buf := make([]byte, 8192)
239
+ n, err := r.Read(buf)
240
+ if err != nil && err != io.EOF {
241
+ return 0, errors.Wrap(err, "failed to read webm file")
242
+ }
243
+
244
+ // 尝试查找 Duration 元素(这是一个简化的方法)
245
+ // 实际的 WebM 解析需要完整的 EBML 解析器
246
+ // 这里返回错误,建议使用专门的库
247
+ if n > 0 {
248
+ // 检查 EBML 标识
249
+ if len(buf) >= 4 && binary.BigEndian.Uint32(buf[0:4]) == 0x1A45DFA3 {
250
+ // 这是一个有效的 EBML 文件
251
+ // 但完整解析需要更复杂的逻辑
252
+ return 0, errors.New("webm duration parsing requires full EBML parser (consider using ffprobe for webm files)")
253
+ }
254
+ }
255
+
256
+ return 0, errors.New("failed to parse webm file")
257
+ }
258
+
259
+ // getAACDuration 解析 AAC (ADTS格式) 文件以获取时长。
260
+ // 使用 gomedia 库来解析 AAC ADTS 帧
261
+ func getAACDuration(r io.ReadSeeker) (float64, error) {
262
+ if _, err := r.Seek(0, io.SeekStart); err != nil {
263
+ return 0, errors.Wrap(err, "failed to seek aac file")
264
+ }
265
+
266
+ // 读取整个文件内容
267
+ data, err := io.ReadAll(r)
268
+ if err != nil {
269
+ return 0, errors.Wrap(err, "failed to read aac file")
270
+ }
271
+
272
+ var totalFrames int64
273
+ var sampleRate int
274
+
275
+ // 使用 gomedia 的 SplitAACFrame 函数来分割 AAC 帧
276
+ codec.SplitAACFrame(data, func(aac []byte) {
277
+ // 解析 ADTS 头部以获取采样率信息
278
+ if len(aac) >= 7 {
279
+ // 使用 ConvertADTSToASC 来获取音频配置信息
280
+ asc, err := codec.ConvertADTSToASC(aac)
281
+ if err == nil && sampleRate == 0 {
282
+ sampleRate = codec.AACSampleIdxToSample(int(asc.Sample_freq_index))
283
+ }
284
+ totalFrames++
285
+ }
286
+ })
287
+
288
+ if sampleRate == 0 || totalFrames == 0 {
289
+ return 0, errors.New("no valid aac frames found")
290
+ }
291
+
292
+ // 每个 AAC ADTS 帧包含 1024 个采样
293
+ totalSamples := totalFrames * 1024
294
+ duration := float64(totalSamples) / float64(sampleRate)
295
+ return duration, nil
296
+ }
common/constants.go ADDED
@@ -0,0 +1,204 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package common
2
+
3
+ import (
4
+ //"os"
5
+ //"strconv"
6
+ "sync"
7
+ "time"
8
+
9
+ "github.com/google/uuid"
10
+ )
11
+
12
+ var StartTime = time.Now().Unix() // unit: second
13
+ var Version = "v0.0.0" // this hard coding will be replaced automatically when building, no need to manually change
14
+ var SystemName = "New API"
15
+ var Footer = ""
16
+ var Logo = ""
17
+ var TopUpLink = ""
18
+
19
+ // var ChatLink = ""
20
+ // var ChatLink2 = ""
21
+ var QuotaPerUnit = 500 * 1000.0 // $0.002 / 1K tokens
22
+ // 保留旧变量以兼容历史逻辑,实际展示由 general_setting.quota_display_type 控制
23
+ var DisplayInCurrencyEnabled = true
24
+ var DisplayTokenStatEnabled = true
25
+ var DrawingEnabled = true
26
+ var TaskEnabled = true
27
+ var DataExportEnabled = true
28
+ var DataExportInterval = 5 // unit: minute
29
+ var DataExportDefaultTime = "hour" // unit: minute
30
+ var DefaultCollapseSidebar = false // default value of collapse sidebar
31
+
32
+ // Any options with "Secret", "Token" in its key won't be return by GetOptions
33
+
34
+ var SessionSecret = uuid.New().String()
35
+ var CryptoSecret = uuid.New().String()
36
+
37
+ var OptionMap map[string]string
38
+ var OptionMapRWMutex sync.RWMutex
39
+
40
+ var ItemsPerPage = 10
41
+ var MaxRecentItems = 100
42
+
43
+ var PasswordLoginEnabled = true
44
+ var PasswordRegisterEnabled = true
45
+ var EmailVerificationEnabled = false
46
+ var GitHubOAuthEnabled = false
47
+ var LinuxDOOAuthEnabled = false
48
+ var WeChatAuthEnabled = false
49
+ var TelegramOAuthEnabled = false
50
+ var TurnstileCheckEnabled = false
51
+ var RegisterEnabled = true
52
+
53
+ var EmailDomainRestrictionEnabled = false // 是否启用邮箱域名限制
54
+ var EmailAliasRestrictionEnabled = false // 是否启用邮箱别名限制
55
+ var EmailDomainWhitelist = []string{
56
+ "gmail.com",
57
+ "163.com",
58
+ "126.com",
59
+ "qq.com",
60
+ "outlook.com",
61
+ "hotmail.com",
62
+ "icloud.com",
63
+ "yahoo.com",
64
+ "foxmail.com",
65
+ }
66
+ var EmailLoginAuthServerList = []string{
67
+ "smtp.sendcloud.net",
68
+ "smtp.azurecomm.net",
69
+ }
70
+
71
+ var DebugEnabled bool
72
+ var MemoryCacheEnabled bool
73
+
74
+ var LogConsumeEnabled = true
75
+
76
+ var SMTPServer = ""
77
+ var SMTPPort = 587
78
+ var SMTPSSLEnabled = false
79
+ var SMTPAccount = ""
80
+ var SMTPFrom = ""
81
+ var SMTPToken = ""
82
+
83
+ var GitHubClientId = ""
84
+ var GitHubClientSecret = ""
85
+ var LinuxDOClientId = ""
86
+ var LinuxDOClientSecret = ""
87
+ var LinuxDOMinimumTrustLevel = 0
88
+
89
+ var WeChatServerAddress = ""
90
+ var WeChatServerToken = ""
91
+ var WeChatAccountQRCodeImageURL = ""
92
+
93
+ var TurnstileSiteKey = ""
94
+ var TurnstileSecretKey = ""
95
+
96
+ var TelegramBotToken = ""
97
+ var TelegramBotName = ""
98
+
99
+ var QuotaForNewUser = 0
100
+ var QuotaForInviter = 0
101
+ var QuotaForInvitee = 0
102
+ var ChannelDisableThreshold = 5.0
103
+ var AutomaticDisableChannelEnabled = false
104
+ var AutomaticEnableChannelEnabled = false
105
+ var QuotaRemindThreshold = 1000
106
+ var PreConsumedQuota = 500
107
+
108
+ var RetryTimes = 0
109
+
110
+ //var RootUserEmail = ""
111
+
112
+ var IsMasterNode bool
113
+
114
+ var requestInterval int
115
+ var RequestInterval time.Duration
116
+
117
+ var SyncFrequency int // unit is second
118
+
119
+ var BatchUpdateEnabled = false
120
+ var BatchUpdateInterval int
121
+
122
+ var RelayTimeout int // unit is second
123
+
124
+ var GeminiSafetySetting string
125
+
126
+ // https://docs.cohere.com/docs/safety-modes Type; NONE/CONTEXTUAL/STRICT
127
+ var CohereSafetySetting string
128
+
129
+ const (
130
+ RequestIdKey = "X-Oneapi-Request-Id"
131
+ )
132
+
133
+ const (
134
+ RoleGuestUser = 0
135
+ RoleCommonUser = 1
136
+ RoleAdminUser = 10
137
+ RoleRootUser = 100
138
+ )
139
+
140
+ func IsValidateRole(role int) bool {
141
+ return role == RoleGuestUser || role == RoleCommonUser || role == RoleAdminUser || role == RoleRootUser
142
+ }
143
+
144
+ var (
145
+ FileUploadPermission = RoleGuestUser
146
+ FileDownloadPermission = RoleGuestUser
147
+ ImageUploadPermission = RoleGuestUser
148
+ ImageDownloadPermission = RoleGuestUser
149
+ )
150
+
151
+ // All duration's unit is seconds
152
+ // Shouldn't larger then RateLimitKeyExpirationDuration
153
+ var (
154
+ GlobalApiRateLimitEnable bool
155
+ GlobalApiRateLimitNum int
156
+ GlobalApiRateLimitDuration int64
157
+
158
+ GlobalWebRateLimitEnable bool
159
+ GlobalWebRateLimitNum int
160
+ GlobalWebRateLimitDuration int64
161
+
162
+ CriticalRateLimitEnable bool
163
+ CriticalRateLimitNum = 20
164
+ CriticalRateLimitDuration int64 = 20 * 60
165
+
166
+ UploadRateLimitNum = 10
167
+ UploadRateLimitDuration int64 = 60
168
+
169
+ DownloadRateLimitNum = 10
170
+ DownloadRateLimitDuration int64 = 60
171
+ )
172
+
173
+ var RateLimitKeyExpirationDuration = 20 * time.Minute
174
+
175
+ const (
176
+ UserStatusEnabled = 1 // don't use 0, 0 is the default value!
177
+ UserStatusDisabled = 2 // also don't use 0
178
+ )
179
+
180
+ const (
181
+ TokenStatusEnabled = 1 // don't use 0, 0 is the default value!
182
+ TokenStatusDisabled = 2 // also don't use 0
183
+ TokenStatusExpired = 3
184
+ TokenStatusExhausted = 4
185
+ )
186
+
187
+ const (
188
+ RedemptionCodeStatusEnabled = 1 // don't use 0, 0 is the default value!
189
+ RedemptionCodeStatusDisabled = 2 // also don't use 0
190
+ RedemptionCodeStatusUsed = 3 // also don't use 0
191
+ )
192
+
193
+ const (
194
+ ChannelStatusUnknown = 0
195
+ ChannelStatusEnabled = 1 // don't use 0, 0 is the default value!
196
+ ChannelStatusManuallyDisabled = 2 // also don't use 0
197
+ ChannelStatusAutoDisabled = 3
198
+ )
199
+
200
+ const (
201
+ TopUpStatusPending = "pending"
202
+ TopUpStatusSuccess = "success"
203
+ TopUpStatusExpired = "expired"
204
+ )
common/copy.go ADDED
@@ -0,0 +1,19 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package common
2
+
3
+ import (
4
+ "fmt"
5
+
6
+ "github.com/jinzhu/copier"
7
+ )
8
+
9
+ func DeepCopy[T any](src *T) (*T, error) {
10
+ if src == nil {
11
+ return nil, fmt.Errorf("copy source cannot be nil")
12
+ }
13
+ var dst T
14
+ err := copier.CopyWithOption(&dst, src, copier.Option{DeepCopy: true, IgnoreEmpty: true})
15
+ if err != nil {
16
+ return nil, err
17
+ }
18
+ return &dst, nil
19
+ }
common/crypto.go ADDED
@@ -0,0 +1,32 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package common
2
+
3
+ import (
4
+ "crypto/hmac"
5
+ "crypto/sha256"
6
+ "encoding/hex"
7
+
8
+ "golang.org/x/crypto/bcrypt"
9
+ )
10
+
11
+ func GenerateHMACWithKey(key []byte, data string) string {
12
+ h := hmac.New(sha256.New, key)
13
+ h.Write([]byte(data))
14
+ return hex.EncodeToString(h.Sum(nil))
15
+ }
16
+
17
+ func GenerateHMAC(data string) string {
18
+ h := hmac.New(sha256.New, []byte(CryptoSecret))
19
+ h.Write([]byte(data))
20
+ return hex.EncodeToString(h.Sum(nil))
21
+ }
22
+
23
+ func Password2Hash(password string) (string, error) {
24
+ passwordBytes := []byte(password)
25
+ hashedPassword, err := bcrypt.GenerateFromPassword(passwordBytes, bcrypt.DefaultCost)
26
+ return string(hashedPassword), err
27
+ }
28
+
29
+ func ValidatePasswordAndHash(password string, hash string) bool {
30
+ err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
31
+ return err == nil
32
+ }
common/custom-event.go ADDED
@@ -0,0 +1,87 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Copyright 2014 Manu Martinez-Almeida. All rights reserved.
2
+ // Use of this source code is governed by a MIT style
3
+ // license that can be found in the LICENSE file.
4
+
5
+ package common
6
+
7
+ import (
8
+ "fmt"
9
+ "io"
10
+ "net/http"
11
+ "strings"
12
+ "sync"
13
+ )
14
+
15
+ type stringWriter interface {
16
+ io.Writer
17
+ writeString(string) (int, error)
18
+ }
19
+
20
+ type stringWrapper struct {
21
+ io.Writer
22
+ }
23
+
24
+ func (w stringWrapper) writeString(str string) (int, error) {
25
+ return w.Writer.Write([]byte(str))
26
+ }
27
+
28
+ func checkWriter(writer io.Writer) stringWriter {
29
+ if w, ok := writer.(stringWriter); ok {
30
+ return w
31
+ } else {
32
+ return stringWrapper{writer}
33
+ }
34
+ }
35
+
36
+ // Server-Sent Events
37
+ // W3C Working Draft 29 October 2009
38
+ // http://www.w3.org/TR/2009/WD-eventsource-20091029/
39
+
40
+ var contentType = []string{"text/event-stream"}
41
+ var noCache = []string{"no-cache"}
42
+
43
+ var fieldReplacer = strings.NewReplacer(
44
+ "\n", "\\n",
45
+ "\r", "\\r")
46
+
47
+ var dataReplacer = strings.NewReplacer(
48
+ "\n", "\n",
49
+ "\r", "\\r")
50
+
51
+ type CustomEvent struct {
52
+ Event string
53
+ Id string
54
+ Retry uint
55
+ Data interface{}
56
+
57
+ Mutex sync.Mutex
58
+ }
59
+
60
+ func encode(writer io.Writer, event CustomEvent) error {
61
+ w := checkWriter(writer)
62
+ return writeData(w, event.Data)
63
+ }
64
+
65
+ func writeData(w stringWriter, data interface{}) error {
66
+ dataReplacer.WriteString(w, fmt.Sprint(data))
67
+ if strings.HasPrefix(data.(string), "data") {
68
+ w.writeString("\n\n")
69
+ }
70
+ return nil
71
+ }
72
+
73
+ func (r CustomEvent) Render(w http.ResponseWriter) error {
74
+ r.WriteContentType(w)
75
+ return encode(w, r)
76
+ }
77
+
78
+ func (r CustomEvent) WriteContentType(w http.ResponseWriter) {
79
+ r.Mutex.Lock()
80
+ defer r.Mutex.Unlock()
81
+ header := w.Header()
82
+ header["Content-Type"] = contentType
83
+
84
+ if _, exist := header["Cache-Control"]; !exist {
85
+ header["Cache-Control"] = noCache
86
+ }
87
+ }
common/database.go ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package common
2
+
3
+ const (
4
+ DatabaseTypeMySQL = "mysql"
5
+ DatabaseTypeSQLite = "sqlite"
6
+ DatabaseTypePostgreSQL = "postgres"
7
+ )
8
+
9
+ var UsingSQLite = false
10
+ var UsingPostgreSQL = false
11
+ var LogSqlType = DatabaseTypeSQLite // Default to SQLite for logging SQL queries
12
+ var UsingMySQL = false
13
+ var UsingClickHouse = false
14
+
15
+ var SQLitePath = "one-api.db?_busy_timeout=30000"
common/email-outlook-auth.go ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package common
2
+
3
+ import (
4
+ "errors"
5
+ "net/smtp"
6
+ "strings"
7
+ )
8
+
9
+ type outlookAuth struct {
10
+ username, password string
11
+ }
12
+
13
+ func LoginAuth(username, password string) smtp.Auth {
14
+ return &outlookAuth{username, password}
15
+ }
16
+
17
+ func (a *outlookAuth) Start(_ *smtp.ServerInfo) (string, []byte, error) {
18
+ return "LOGIN", []byte{}, nil
19
+ }
20
+
21
+ func (a *outlookAuth) Next(fromServer []byte, more bool) ([]byte, error) {
22
+ if more {
23
+ switch string(fromServer) {
24
+ case "Username:":
25
+ return []byte(a.username), nil
26
+ case "Password:":
27
+ return []byte(a.password), nil
28
+ default:
29
+ return nil, errors.New("unknown fromServer")
30
+ }
31
+ }
32
+ return nil, nil
33
+ }
34
+
35
+ func isOutlookServer(server string) bool {
36
+ // 兼容多地区的outlook邮箱和ofb邮箱
37
+ // 其实应该加一个Option来区分是否用LOGIN的方式登录
38
+ // 先临时兼容一下
39
+ return strings.Contains(server, "outlook") || strings.Contains(server, "onmicrosoft")
40
+ }
common/email.go ADDED
@@ -0,0 +1,93 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package common
2
+
3
+ import (
4
+ "crypto/tls"
5
+ "encoding/base64"
6
+ "fmt"
7
+ "net/smtp"
8
+ "slices"
9
+ "strings"
10
+ "time"
11
+ )
12
+
13
+ func generateMessageID() (string, error) {
14
+ split := strings.Split(SMTPFrom, "@")
15
+ if len(split) < 2 {
16
+ return "", fmt.Errorf("invalid SMTP account")
17
+ }
18
+ domain := strings.Split(SMTPFrom, "@")[1]
19
+ return fmt.Sprintf("<%d.%s@%s>", time.Now().UnixNano(), GetRandomString(12), domain), nil
20
+ }
21
+
22
+ func SendEmail(subject string, receiver string, content string) error {
23
+ if SMTPFrom == "" { // for compatibility
24
+ SMTPFrom = SMTPAccount
25
+ }
26
+ id, err2 := generateMessageID()
27
+ if err2 != nil {
28
+ return err2
29
+ }
30
+ if SMTPServer == "" && SMTPAccount == "" {
31
+ return fmt.Errorf("SMTP 服务器未配置")
32
+ }
33
+ encodedSubject := fmt.Sprintf("=?UTF-8?B?%s?=", base64.StdEncoding.EncodeToString([]byte(subject)))
34
+ mail := []byte(fmt.Sprintf("To: %s\r\n"+
35
+ "From: %s<%s>\r\n"+
36
+ "Subject: %s\r\n"+
37
+ "Date: %s\r\n"+
38
+ "Message-ID: %s\r\n"+ // 添加 Message-ID 头
39
+ "Content-Type: text/html; charset=UTF-8\r\n\r\n%s\r\n",
40
+ receiver, SystemName, SMTPFrom, encodedSubject, time.Now().Format(time.RFC1123Z), id, content))
41
+ auth := smtp.PlainAuth("", SMTPAccount, SMTPToken, SMTPServer)
42
+ addr := fmt.Sprintf("%s:%d", SMTPServer, SMTPPort)
43
+ to := strings.Split(receiver, ";")
44
+ var err error
45
+ if SMTPPort == 465 || SMTPSSLEnabled {
46
+ tlsConfig := &tls.Config{
47
+ InsecureSkipVerify: true,
48
+ ServerName: SMTPServer,
49
+ }
50
+ conn, err := tls.Dial("tcp", fmt.Sprintf("%s:%d", SMTPServer, SMTPPort), tlsConfig)
51
+ if err != nil {
52
+ return err
53
+ }
54
+ client, err := smtp.NewClient(conn, SMTPServer)
55
+ if err != nil {
56
+ return err
57
+ }
58
+ defer client.Close()
59
+ if err = client.Auth(auth); err != nil {
60
+ return err
61
+ }
62
+ if err = client.Mail(SMTPFrom); err != nil {
63
+ return err
64
+ }
65
+ receiverEmails := strings.Split(receiver, ";")
66
+ for _, receiver := range receiverEmails {
67
+ if err = client.Rcpt(receiver); err != nil {
68
+ return err
69
+ }
70
+ }
71
+ w, err := client.Data()
72
+ if err != nil {
73
+ return err
74
+ }
75
+ _, err = w.Write(mail)
76
+ if err != nil {
77
+ return err
78
+ }
79
+ err = w.Close()
80
+ if err != nil {
81
+ return err
82
+ }
83
+ } else if isOutlookServer(SMTPAccount) || slices.Contains(EmailLoginAuthServerList, SMTPServer) {
84
+ auth = LoginAuth(SMTPAccount, SMTPToken)
85
+ err = smtp.SendMail(addr, auth, SMTPFrom, to, mail)
86
+ } else {
87
+ err = smtp.SendMail(addr, auth, SMTPFrom, to, mail)
88
+ }
89
+ if err != nil {
90
+ SysError(fmt.Sprintf("failed to send email to %s: %v", receiver, err))
91
+ }
92
+ return err
93
+ }
common/embed-file-system.go ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package common
2
+
3
+ import (
4
+ "embed"
5
+ "io/fs"
6
+ "net/http"
7
+ "os"
8
+
9
+ "github.com/gin-contrib/static"
10
+ )
11
+
12
+ // Credit: https://github.com/gin-contrib/static/issues/19
13
+
14
+ type embedFileSystem struct {
15
+ http.FileSystem
16
+ }
17
+
18
+ func (e *embedFileSystem) Exists(prefix string, path string) bool {
19
+ _, err := e.Open(path)
20
+ if err != nil {
21
+ return false
22
+ }
23
+ return true
24
+ }
25
+
26
+ func (e *embedFileSystem) Open(name string) (http.File, error) {
27
+ if name == "/" {
28
+ // This will make sure the index page goes to NoRouter handler,
29
+ // which will use the replaced index bytes with analytic codes.
30
+ return nil, os.ErrNotExist
31
+ }
32
+ return e.FileSystem.Open(name)
33
+ }
34
+
35
+ func EmbedFolder(fsEmbed embed.FS, targetPath string) static.ServeFileSystem {
36
+ efs, err := fs.Sub(fsEmbed, targetPath)
37
+ if err != nil {
38
+ panic(err)
39
+ }
40
+ return &embedFileSystem{
41
+ FileSystem: http.FS(efs),
42
+ }
43
+ }
common/endpoint_defaults.go ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package common
2
+
3
+ import "github.com/QuantumNous/new-api/constant"
4
+
5
+ // EndpointInfo 描述单个端点的默认请求信息
6
+ // path: 上游路径
7
+ // method: HTTP 请求方式,例如 POST/GET
8
+ // 目前均为 POST,后续可扩展
9
+ //
10
+ // json 标签用于直接序列化到 API 输出
11
+ // 例如:{"path":"/v1/chat/completions","method":"POST"}
12
+
13
+ type EndpointInfo struct {
14
+ Path string `json:"path"`
15
+ Method string `json:"method"`
16
+ }
17
+
18
+ // defaultEndpointInfoMap 保存内置端点的默认 Path 与 Method
19
+ var defaultEndpointInfoMap = map[constant.EndpointType]EndpointInfo{
20
+ constant.EndpointTypeOpenAI: {Path: "/v1/chat/completions", Method: "POST"},
21
+ constant.EndpointTypeOpenAIResponse: {Path: "/v1/responses", Method: "POST"},
22
+ constant.EndpointTypeAnthropic: {Path: "/v1/messages", Method: "POST"},
23
+ constant.EndpointTypeGemini: {Path: "/v1beta/models/{model}:generateContent", Method: "POST"},
24
+ constant.EndpointTypeJinaRerank: {Path: "/rerank", Method: "POST"},
25
+ constant.EndpointTypeImageGeneration: {Path: "/v1/images/generations", Method: "POST"},
26
+ constant.EndpointTypeEmbeddings: {Path: "/v1/embeddings", Method: "POST"},
27
+ }
28
+
29
+ // GetDefaultEndpointInfo 返回指定端点类型的默认信息以及是否存在
30
+ func GetDefaultEndpointInfo(et constant.EndpointType) (EndpointInfo, bool) {
31
+ info, ok := defaultEndpointInfoMap[et]
32
+ return info, ok
33
+ }
common/endpoint_type.go ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package common
2
+
3
+ import "github.com/QuantumNous/new-api/constant"
4
+
5
+ // GetEndpointTypesByChannelType 获取渠道最优先端点类型(所有的渠道都支持 OpenAI 端点)
6
+ func GetEndpointTypesByChannelType(channelType int, modelName string) []constant.EndpointType {
7
+ var endpointTypes []constant.EndpointType
8
+ switch channelType {
9
+ case constant.ChannelTypeJina:
10
+ endpointTypes = []constant.EndpointType{constant.EndpointTypeJinaRerank}
11
+ //case constant.ChannelTypeMidjourney, constant.ChannelTypeMidjourneyPlus:
12
+ // endpointTypes = []constant.EndpointType{constant.EndpointTypeMidjourney}
13
+ //case constant.ChannelTypeSunoAPI:
14
+ // endpointTypes = []constant.EndpointType{constant.EndpointTypeSuno}
15
+ //case constant.ChannelTypeKling:
16
+ // endpointTypes = []constant.EndpointType{constant.EndpointTypeKling}
17
+ //case constant.ChannelTypeJimeng:
18
+ // endpointTypes = []constant.EndpointType{constant.EndpointTypeJimeng}
19
+ case constant.ChannelTypeAws:
20
+ fallthrough
21
+ case constant.ChannelTypeAnthropic:
22
+ endpointTypes = []constant.EndpointType{constant.EndpointTypeAnthropic, constant.EndpointTypeOpenAI}
23
+ case constant.ChannelTypeVertexAi:
24
+ fallthrough
25
+ case constant.ChannelTypeGemini:
26
+ endpointTypes = []constant.EndpointType{constant.EndpointTypeGemini, constant.EndpointTypeOpenAI}
27
+ case constant.ChannelTypeOpenRouter: // OpenRouter 只支持 OpenAI 端点
28
+ endpointTypes = []constant.EndpointType{constant.EndpointTypeOpenAI}
29
+ case constant.ChannelTypeSora:
30
+ endpointTypes = []constant.EndpointType{constant.EndpointTypeOpenAIVideo}
31
+ default:
32
+ if IsOpenAIResponseOnlyModel(modelName) {
33
+ endpointTypes = []constant.EndpointType{constant.EndpointTypeOpenAIResponse}
34
+ } else {
35
+ endpointTypes = []constant.EndpointType{constant.EndpointTypeOpenAI}
36
+ }
37
+ }
38
+ if IsImageGenerationModel(modelName) {
39
+ // add to first
40
+ endpointTypes = append([]constant.EndpointType{constant.EndpointTypeImageGeneration}, endpointTypes...)
41
+ }
42
+ return endpointTypes
43
+ }
common/env.go ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package common
2
+
3
+ import (
4
+ "fmt"
5
+ "os"
6
+ "strconv"
7
+ )
8
+
9
+ func GetEnvOrDefault(env string, defaultValue int) int {
10
+ if env == "" || os.Getenv(env) == "" {
11
+ return defaultValue
12
+ }
13
+ num, err := strconv.Atoi(os.Getenv(env))
14
+ if err != nil {
15
+ SysError(fmt.Sprintf("failed to parse %s: %s, using default value: %d", env, err.Error(), defaultValue))
16
+ return defaultValue
17
+ }
18
+ return num
19
+ }
20
+
21
+ func GetEnvOrDefaultString(env string, defaultValue string) string {
22
+ if env == "" || os.Getenv(env) == "" {
23
+ return defaultValue
24
+ }
25
+ return os.Getenv(env)
26
+ }
27
+
28
+ func GetEnvOrDefaultBool(env string, defaultValue bool) bool {
29
+ if env == "" || os.Getenv(env) == "" {
30
+ return defaultValue
31
+ }
32
+ b, err := strconv.ParseBool(os.Getenv(env))
33
+ if err != nil {
34
+ SysError(fmt.Sprintf("failed to parse %s: %s, using default value: %t", env, err.Error(), defaultValue))
35
+ return defaultValue
36
+ }
37
+ return b
38
+ }
common/gin.go ADDED
@@ -0,0 +1,234 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package common
2
+
3
+ import (
4
+ "bytes"
5
+ "errors"
6
+ "io"
7
+ "mime"
8
+ "mime/multipart"
9
+ "net/http"
10
+ "net/url"
11
+ "strings"
12
+ "time"
13
+
14
+ "github.com/QuantumNous/new-api/constant"
15
+
16
+ "github.com/gin-gonic/gin"
17
+ )
18
+
19
+ const KeyRequestBody = "key_request_body"
20
+
21
+ func GetRequestBody(c *gin.Context) ([]byte, error) {
22
+ requestBody, _ := c.Get(KeyRequestBody)
23
+ if requestBody != nil {
24
+ return requestBody.([]byte), nil
25
+ }
26
+ requestBody, err := io.ReadAll(c.Request.Body)
27
+ if err != nil {
28
+ return nil, err
29
+ }
30
+ _ = c.Request.Body.Close()
31
+ c.Set(KeyRequestBody, requestBody)
32
+ return requestBody.([]byte), nil
33
+ }
34
+
35
+ func UnmarshalBodyReusable(c *gin.Context, v any) error {
36
+ requestBody, err := GetRequestBody(c)
37
+ if err != nil {
38
+ return err
39
+ }
40
+ //if DebugEnabled {
41
+ // println("UnmarshalBodyReusable request body:", string(requestBody))
42
+ //}
43
+ contentType := c.Request.Header.Get("Content-Type")
44
+ if strings.HasPrefix(contentType, "application/json") {
45
+ err = Unmarshal(requestBody, v)
46
+ } else if strings.Contains(contentType, gin.MIMEPOSTForm) {
47
+ err = parseFormData(requestBody, v)
48
+ } else if strings.Contains(contentType, gin.MIMEMultipartPOSTForm) {
49
+ err = parseMultipartFormData(c, requestBody, v)
50
+ } else {
51
+ // skip for now
52
+ // TODO: someday non json request have variant model, we will need to implementation this
53
+ }
54
+ if err != nil {
55
+ return err
56
+ }
57
+ // Reset request body
58
+ c.Request.Body = io.NopCloser(bytes.NewBuffer(requestBody))
59
+ return nil
60
+ }
61
+
62
+ func SetContextKey(c *gin.Context, key constant.ContextKey, value any) {
63
+ c.Set(string(key), value)
64
+ }
65
+
66
+ func GetContextKey(c *gin.Context, key constant.ContextKey) (any, bool) {
67
+ return c.Get(string(key))
68
+ }
69
+
70
+ func GetContextKeyString(c *gin.Context, key constant.ContextKey) string {
71
+ return c.GetString(string(key))
72
+ }
73
+
74
+ func GetContextKeyInt(c *gin.Context, key constant.ContextKey) int {
75
+ return c.GetInt(string(key))
76
+ }
77
+
78
+ func GetContextKeyBool(c *gin.Context, key constant.ContextKey) bool {
79
+ return c.GetBool(string(key))
80
+ }
81
+
82
+ func GetContextKeyStringSlice(c *gin.Context, key constant.ContextKey) []string {
83
+ return c.GetStringSlice(string(key))
84
+ }
85
+
86
+ func GetContextKeyStringMap(c *gin.Context, key constant.ContextKey) map[string]any {
87
+ return c.GetStringMap(string(key))
88
+ }
89
+
90
+ func GetContextKeyTime(c *gin.Context, key constant.ContextKey) time.Time {
91
+ return c.GetTime(string(key))
92
+ }
93
+
94
+ func GetContextKeyType[T any](c *gin.Context, key constant.ContextKey) (T, bool) {
95
+ if value, ok := c.Get(string(key)); ok {
96
+ if v, ok := value.(T); ok {
97
+ return v, true
98
+ }
99
+ }
100
+ var t T
101
+ return t, false
102
+ }
103
+
104
+ func ApiError(c *gin.Context, err error) {
105
+ c.JSON(http.StatusOK, gin.H{
106
+ "success": false,
107
+ "message": err.Error(),
108
+ })
109
+ }
110
+
111
+ func ApiErrorMsg(c *gin.Context, msg string) {
112
+ c.JSON(http.StatusOK, gin.H{
113
+ "success": false,
114
+ "message": msg,
115
+ })
116
+ }
117
+
118
+ func ApiSuccess(c *gin.Context, data any) {
119
+ c.JSON(http.StatusOK, gin.H{
120
+ "success": true,
121
+ "message": "",
122
+ "data": data,
123
+ })
124
+ }
125
+
126
+ func ParseMultipartFormReusable(c *gin.Context) (*multipart.Form, error) {
127
+ requestBody, err := GetRequestBody(c)
128
+ if err != nil {
129
+ return nil, err
130
+ }
131
+
132
+ contentType := c.Request.Header.Get("Content-Type")
133
+ boundary, err := parseBoundary(contentType)
134
+ if err != nil {
135
+ return nil, err
136
+ }
137
+
138
+ reader := multipart.NewReader(bytes.NewReader(requestBody), boundary)
139
+ form, err := reader.ReadForm(multipartMemoryLimit())
140
+ if err != nil {
141
+ return nil, err
142
+ }
143
+
144
+ // Reset request body
145
+ c.Request.Body = io.NopCloser(bytes.NewBuffer(requestBody))
146
+ return form, nil
147
+ }
148
+
149
+ func processFormMap(formMap map[string]any, v any) error {
150
+ jsonData, err := Marshal(formMap)
151
+ if err != nil {
152
+ return err
153
+ }
154
+
155
+ err = Unmarshal(jsonData, v)
156
+ if err != nil {
157
+ return err
158
+ }
159
+
160
+ return nil
161
+ }
162
+
163
+ func parseFormData(data []byte, v any) error {
164
+ values, err := url.ParseQuery(string(data))
165
+ if err != nil {
166
+ return err
167
+ }
168
+ formMap := make(map[string]any)
169
+ for key, vals := range values {
170
+ if len(vals) == 1 {
171
+ formMap[key] = vals[0]
172
+ } else {
173
+ formMap[key] = vals
174
+ }
175
+ }
176
+
177
+ return processFormMap(formMap, v)
178
+ }
179
+
180
+ func parseMultipartFormData(c *gin.Context, data []byte, v any) error {
181
+ contentType := c.Request.Header.Get("Content-Type")
182
+ boundary, err := parseBoundary(contentType)
183
+ if err != nil {
184
+ if errors.Is(err, errBoundaryNotFound) {
185
+ return Unmarshal(data, v) // Fallback to JSON
186
+ }
187
+ return err
188
+ }
189
+
190
+ reader := multipart.NewReader(bytes.NewReader(data), boundary)
191
+ form, err := reader.ReadForm(multipartMemoryLimit())
192
+ if err != nil {
193
+ return err
194
+ }
195
+ defer form.RemoveAll()
196
+ formMap := make(map[string]any)
197
+ for key, vals := range form.Value {
198
+ if len(vals) == 1 {
199
+ formMap[key] = vals[0]
200
+ } else {
201
+ formMap[key] = vals
202
+ }
203
+ }
204
+
205
+ return processFormMap(formMap, v)
206
+ }
207
+
208
+ var errBoundaryNotFound = errors.New("multipart boundary not found")
209
+
210
+ // parseBoundary extracts the multipart boundary from the Content-Type header using mime.ParseMediaType
211
+ func parseBoundary(contentType string) (string, error) {
212
+ if contentType == "" {
213
+ return "", errBoundaryNotFound
214
+ }
215
+ // Boundary-UUID / boundary-------xxxxxx
216
+ _, params, err := mime.ParseMediaType(contentType)
217
+ if err != nil {
218
+ return "", err
219
+ }
220
+ boundary, ok := params["boundary"]
221
+ if !ok || boundary == "" {
222
+ return "", errBoundaryNotFound
223
+ }
224
+ return boundary, nil
225
+ }
226
+
227
+ // multipartMemoryLimit returns the configured multipart memory limit in bytes
228
+ func multipartMemoryLimit() int64 {
229
+ limitMB := constant.MaxFileDownloadMB
230
+ if limitMB <= 0 {
231
+ limitMB = 32
232
+ }
233
+ return int64(limitMB) << 20
234
+ }
common/go-channel.go ADDED
@@ -0,0 +1,53 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package common
2
+
3
+ import (
4
+ "time"
5
+ )
6
+
7
+ func SafeSendBool(ch chan bool, value bool) (closed bool) {
8
+ defer func() {
9
+ // Recover from panic if one occured. A panic would mean the channel was closed.
10
+ if recover() != nil {
11
+ closed = true
12
+ }
13
+ }()
14
+
15
+ // This will panic if the channel is closed.
16
+ ch <- value
17
+
18
+ // If the code reaches here, then the channel was not closed.
19
+ return false
20
+ }
21
+
22
+ func SafeSendString(ch chan string, value string) (closed bool) {
23
+ defer func() {
24
+ // Recover from panic if one occured. A panic would mean the channel was closed.
25
+ if recover() != nil {
26
+ closed = true
27
+ }
28
+ }()
29
+
30
+ // This will panic if the channel is closed.
31
+ ch <- value
32
+
33
+ // If the code reaches here, then the channel was not closed.
34
+ return false
35
+ }
36
+
37
+ // SafeSendStringTimeout send, return true, else return false
38
+ func SafeSendStringTimeout(ch chan string, value string, timeout int) (closed bool) {
39
+ defer func() {
40
+ // Recover from panic if one occured. A panic would mean the channel was closed.
41
+ if recover() != nil {
42
+ closed = false
43
+ }
44
+ }()
45
+
46
+ // This will panic if the channel is closed.
47
+ select {
48
+ case ch <- value:
49
+ return true
50
+ case <-time.After(time.Duration(timeout) * time.Second):
51
+ return false
52
+ }
53
+ }
common/gopool.go ADDED
@@ -0,0 +1,25 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package common
2
+
3
+ import (
4
+ "context"
5
+ "fmt"
6
+ "math"
7
+
8
+ "github.com/bytedance/gopkg/util/gopool"
9
+ )
10
+
11
+ var relayGoPool gopool.Pool
12
+
13
+ func init() {
14
+ relayGoPool = gopool.NewPool("gopool.RelayPool", math.MaxInt32, gopool.NewConfig())
15
+ relayGoPool.SetPanicHandler(func(ctx context.Context, i interface{}) {
16
+ if stopChan, ok := ctx.Value("stop_chan").(chan bool); ok {
17
+ SafeSendBool(stopChan, true)
18
+ }
19
+ SysError(fmt.Sprintf("panic in gopool.RelayPool: %v", i))
20
+ })
21
+ }
22
+
23
+ func RelayCtxGo(ctx context.Context, f func()) {
24
+ relayGoPool.CtxGo(ctx, f)
25
+ }
common/hash.go ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package common
2
+
3
+ import (
4
+ "crypto/hmac"
5
+ "crypto/sha1"
6
+ "crypto/sha256"
7
+ "encoding/hex"
8
+ )
9
+
10
+ func Sha256Raw(data []byte) []byte {
11
+ h := sha256.New()
12
+ h.Write(data)
13
+ return h.Sum(nil)
14
+ }
15
+
16
+ func Sha1Raw(data []byte) []byte {
17
+ h := sha1.New()
18
+ h.Write(data)
19
+ return h.Sum(nil)
20
+ }
21
+
22
+ func Sha1(data []byte) string {
23
+ return hex.EncodeToString(Sha1Raw(data))
24
+ }
25
+
26
+ func HmacSha256Raw(message, key []byte) []byte {
27
+ h := hmac.New(sha256.New, key)
28
+ h.Write(message)
29
+ return h.Sum(nil)
30
+ }
31
+
32
+ func HmacSha256(message, key string) string {
33
+ return hex.EncodeToString(HmacSha256Raw([]byte(message), []byte(key)))
34
+ }
common/init.go ADDED
@@ -0,0 +1,144 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package common
2
+
3
+ import (
4
+ "flag"
5
+ "fmt"
6
+ "log"
7
+ "os"
8
+ "path/filepath"
9
+ "strconv"
10
+ "strings"
11
+ "time"
12
+
13
+ "github.com/QuantumNous/new-api/constant"
14
+ )
15
+
16
+ var (
17
+ Port = flag.Int("port", 3000, "the listening port")
18
+ PrintVersion = flag.Bool("version", false, "print version and exit")
19
+ PrintHelp = flag.Bool("help", false, "print help and exit")
20
+ LogDir = flag.String("log-dir", "./logs", "specify the log directory")
21
+ )
22
+
23
+ func printHelp() {
24
+ fmt.Println("NewAPI(Based OneAPI) " + Version + " - The next-generation LLM gateway and AI asset management system supports multiple languages.")
25
+ fmt.Println("Original Project: OneAPI by JustSong - https://github.com/songquanpeng/one-api")
26
+ fmt.Println("Maintainer: QuantumNous - https://github.com/QuantumNous/new-api")
27
+ fmt.Println("Usage: newapi [--port <port>] [--log-dir <log directory>] [--version] [--help]")
28
+ }
29
+
30
+ func InitEnv() {
31
+ flag.Parse()
32
+
33
+ envVersion := os.Getenv("VERSION")
34
+ if envVersion != "" {
35
+ Version = envVersion
36
+ }
37
+
38
+ if *PrintVersion {
39
+ fmt.Println(Version)
40
+ os.Exit(0)
41
+ }
42
+
43
+ if *PrintHelp {
44
+ printHelp()
45
+ os.Exit(0)
46
+ }
47
+
48
+ if os.Getenv("SESSION_SECRET") != "" {
49
+ ss := os.Getenv("SESSION_SECRET")
50
+ if ss == "random_string" {
51
+ log.Println("WARNING: SESSION_SECRET is set to the default value 'random_string', please change it to a random string.")
52
+ log.Println("警告:SESSION_SECRET被设置为默认值'random_string',请修改为随机字符串。")
53
+ log.Fatal("Please set SESSION_SECRET to a random string.")
54
+ } else {
55
+ SessionSecret = ss
56
+ }
57
+ }
58
+ if os.Getenv("CRYPTO_SECRET") != "" {
59
+ CryptoSecret = os.Getenv("CRYPTO_SECRET")
60
+ } else {
61
+ CryptoSecret = SessionSecret
62
+ }
63
+ if os.Getenv("SQLITE_PATH") != "" {
64
+ SQLitePath = os.Getenv("SQLITE_PATH")
65
+ }
66
+ if *LogDir != "" {
67
+ var err error
68
+ *LogDir, err = filepath.Abs(*LogDir)
69
+ if err != nil {
70
+ log.Fatal(err)
71
+ }
72
+ if _, err := os.Stat(*LogDir); os.IsNotExist(err) {
73
+ err = os.Mkdir(*LogDir, 0777)
74
+ if err != nil {
75
+ log.Fatal(err)
76
+ }
77
+ }
78
+ }
79
+
80
+ // Initialize variables from constants.go that were using environment variables
81
+ DebugEnabled = os.Getenv("DEBUG") == "true"
82
+ MemoryCacheEnabled = os.Getenv("MEMORY_CACHE_ENABLED") == "true"
83
+ IsMasterNode = os.Getenv("NODE_TYPE") != "slave"
84
+
85
+ // Parse requestInterval and set RequestInterval
86
+ requestInterval, _ = strconv.Atoi(os.Getenv("POLLING_INTERVAL"))
87
+ RequestInterval = time.Duration(requestInterval) * time.Second
88
+
89
+ // Initialize variables with GetEnvOrDefault
90
+ SyncFrequency = GetEnvOrDefault("SYNC_FREQUENCY", 60)
91
+ BatchUpdateInterval = GetEnvOrDefault("BATCH_UPDATE_INTERVAL", 5)
92
+ RelayTimeout = GetEnvOrDefault("RELAY_TIMEOUT", 0)
93
+
94
+ // Initialize string variables with GetEnvOrDefaultString
95
+ GeminiSafetySetting = GetEnvOrDefaultString("GEMINI_SAFETY_SETTING", "BLOCK_NONE")
96
+ CohereSafetySetting = GetEnvOrDefaultString("COHERE_SAFETY_SETTING", "NONE")
97
+
98
+ // Initialize rate limit variables
99
+ GlobalApiRateLimitEnable = GetEnvOrDefaultBool("GLOBAL_API_RATE_LIMIT_ENABLE", true)
100
+ GlobalApiRateLimitNum = GetEnvOrDefault("GLOBAL_API_RATE_LIMIT", 180)
101
+ GlobalApiRateLimitDuration = int64(GetEnvOrDefault("GLOBAL_API_RATE_LIMIT_DURATION", 180))
102
+
103
+ GlobalWebRateLimitEnable = GetEnvOrDefaultBool("GLOBAL_WEB_RATE_LIMIT_ENABLE", true)
104
+ GlobalWebRateLimitNum = GetEnvOrDefault("GLOBAL_WEB_RATE_LIMIT", 60)
105
+ GlobalWebRateLimitDuration = int64(GetEnvOrDefault("GLOBAL_WEB_RATE_LIMIT_DURATION", 180))
106
+
107
+ CriticalRateLimitEnable = GetEnvOrDefaultBool("CRITICAL_RATE_LIMIT_ENABLE", true)
108
+ CriticalRateLimitNum = GetEnvOrDefault("CRITICAL_RATE_LIMIT", 20)
109
+ CriticalRateLimitDuration = int64(GetEnvOrDefault("CRITICAL_RATE_LIMIT_DURATION", 20*60))
110
+ initConstantEnv()
111
+ }
112
+
113
+ func initConstantEnv() {
114
+ constant.StreamingTimeout = GetEnvOrDefault("STREAMING_TIMEOUT", 300)
115
+ constant.DifyDebug = GetEnvOrDefaultBool("DIFY_DEBUG", true)
116
+ constant.MaxFileDownloadMB = GetEnvOrDefault("MAX_FILE_DOWNLOAD_MB", 20)
117
+ // ForceStreamOption 覆盖请求参数,强制返回usage信息
118
+ constant.ForceStreamOption = GetEnvOrDefaultBool("FORCE_STREAM_OPTION", true)
119
+ constant.CountToken = GetEnvOrDefaultBool("CountToken", true)
120
+ constant.GetMediaToken = GetEnvOrDefaultBool("GET_MEDIA_TOKEN", true)
121
+ constant.GetMediaTokenNotStream = GetEnvOrDefaultBool("GET_MEDIA_TOKEN_NOT_STREAM", false)
122
+ constant.UpdateTask = GetEnvOrDefaultBool("UPDATE_TASK", true)
123
+ constant.AzureDefaultAPIVersion = GetEnvOrDefaultString("AZURE_DEFAULT_API_VERSION", "2025-04-01-preview")
124
+ constant.GeminiVisionMaxImageNum = GetEnvOrDefault("GEMINI_VISION_MAX_IMAGE_NUM", 16)
125
+ constant.NotifyLimitCount = GetEnvOrDefault("NOTIFY_LIMIT_COUNT", 2)
126
+ constant.NotificationLimitDurationMinute = GetEnvOrDefault("NOTIFICATION_LIMIT_DURATION_MINUTE", 10)
127
+ // GenerateDefaultToken 是否生成初始令牌,默认关闭。
128
+ constant.GenerateDefaultToken = GetEnvOrDefaultBool("GENERATE_DEFAULT_TOKEN", false)
129
+ // 是否启用错误日志
130
+ constant.ErrorLogEnabled = GetEnvOrDefaultBool("ERROR_LOG_ENABLED", false)
131
+
132
+ soraPatchStr := GetEnvOrDefaultString("TASK_PRICE_PATCH", "")
133
+ if soraPatchStr != "" {
134
+ var taskPricePatches []string
135
+ soraPatches := strings.Split(soraPatchStr, ",")
136
+ for _, patch := range soraPatches {
137
+ trimmedPatch := strings.TrimSpace(patch)
138
+ if trimmedPatch != "" {
139
+ taskPricePatches = append(taskPricePatches, trimmedPatch)
140
+ }
141
+ }
142
+ constant.TaskPricePatches = taskPricePatches
143
+ }
144
+ }
common/ip.go ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package common
2
+
3
+ import "net"
4
+
5
+ func IsPrivateIP(ip net.IP) bool {
6
+ if ip.IsLoopback() || ip.IsLinkLocalUnicast() || ip.IsLinkLocalMulticast() {
7
+ return true
8
+ }
9
+
10
+ private := []net.IPNet{
11
+ {IP: net.IPv4(10, 0, 0, 0), Mask: net.CIDRMask(8, 32)},
12
+ {IP: net.IPv4(172, 16, 0, 0), Mask: net.CIDRMask(12, 32)},
13
+ {IP: net.IPv4(192, 168, 0, 0), Mask: net.CIDRMask(16, 32)},
14
+ }
15
+
16
+ for _, privateNet := range private {
17
+ if privateNet.Contains(ip) {
18
+ return true
19
+ }
20
+ }
21
+ return false
22
+ }
common/json.go ADDED
@@ -0,0 +1,45 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package common
2
+
3
+ import (
4
+ "bytes"
5
+ "encoding/json"
6
+ "io"
7
+ )
8
+
9
+ func Unmarshal(data []byte, v any) error {
10
+ return json.Unmarshal(data, v)
11
+ }
12
+
13
+ func UnmarshalJsonStr(data string, v any) error {
14
+ return json.Unmarshal(StringToByteSlice(data), v)
15
+ }
16
+
17
+ func DecodeJson(reader io.Reader, v any) error {
18
+ return json.NewDecoder(reader).Decode(v)
19
+ }
20
+
21
+ func Marshal(v any) ([]byte, error) {
22
+ return json.Marshal(v)
23
+ }
24
+
25
+ func GetJsonType(data json.RawMessage) string {
26
+ data = bytes.TrimSpace(data)
27
+ if len(data) == 0 {
28
+ return "unknown"
29
+ }
30
+ firstChar := bytes.TrimSpace(data)[0]
31
+ switch firstChar {
32
+ case '{':
33
+ return "object"
34
+ case '[':
35
+ return "array"
36
+ case '"':
37
+ return "string"
38
+ case 't', 'f':
39
+ return "boolean"
40
+ case 'n':
41
+ return "null"
42
+ default:
43
+ return "number"
44
+ }
45
+ }
common/limiter/limiter.go ADDED
@@ -0,0 +1,90 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package limiter
2
+
3
+ import (
4
+ "context"
5
+ _ "embed"
6
+ "fmt"
7
+ "sync"
8
+
9
+ "github.com/QuantumNous/new-api/common"
10
+ "github.com/go-redis/redis/v8"
11
+ )
12
+
13
+ //go:embed lua/rate_limit.lua
14
+ var rateLimitScript string
15
+
16
+ type RedisLimiter struct {
17
+ client *redis.Client
18
+ limitScriptSHA string
19
+ }
20
+
21
+ var (
22
+ instance *RedisLimiter
23
+ once sync.Once
24
+ )
25
+
26
+ func New(ctx context.Context, r *redis.Client) *RedisLimiter {
27
+ once.Do(func() {
28
+ // 预加载脚本
29
+ limitSHA, err := r.ScriptLoad(ctx, rateLimitScript).Result()
30
+ if err != nil {
31
+ common.SysLog(fmt.Sprintf("Failed to load rate limit script: %v", err))
32
+ }
33
+ instance = &RedisLimiter{
34
+ client: r,
35
+ limitScriptSHA: limitSHA,
36
+ }
37
+ })
38
+
39
+ return instance
40
+ }
41
+
42
+ func (rl *RedisLimiter) Allow(ctx context.Context, key string, opts ...Option) (bool, error) {
43
+ // 默认配置
44
+ config := &Config{
45
+ Capacity: 10,
46
+ Rate: 1,
47
+ Requested: 1,
48
+ }
49
+
50
+ // 应用选项模式
51
+ for _, opt := range opts {
52
+ opt(config)
53
+ }
54
+
55
+ // 执行限流
56
+ result, err := rl.client.EvalSha(
57
+ ctx,
58
+ rl.limitScriptSHA,
59
+ []string{key},
60
+ config.Requested,
61
+ config.Rate,
62
+ config.Capacity,
63
+ ).Int()
64
+
65
+ if err != nil {
66
+ return false, fmt.Errorf("rate limit failed: %w", err)
67
+ }
68
+ return result == 1, nil
69
+ }
70
+
71
+ // Config 配置选项模式
72
+ type Config struct {
73
+ Capacity int64
74
+ Rate int64
75
+ Requested int64
76
+ }
77
+
78
+ type Option func(*Config)
79
+
80
+ func WithCapacity(c int64) Option {
81
+ return func(cfg *Config) { cfg.Capacity = c }
82
+ }
83
+
84
+ func WithRate(r int64) Option {
85
+ return func(cfg *Config) { cfg.Rate = r }
86
+ }
87
+
88
+ func WithRequested(n int64) Option {
89
+ return func(cfg *Config) { cfg.Requested = n }
90
+ }
common/limiter/lua/rate_limit.lua ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ -- 令牌桶限流器
2
+ -- KEYS[1]: 限流器唯一标识
3
+ -- ARGV[1]: 请求令牌数 (通常为1)
4
+ -- ARGV[2]: 令牌生成速率 (每秒)
5
+ -- ARGV[3]: 桶容量
6
+
7
+ local key = KEYS[1]
8
+ local requested = tonumber(ARGV[1])
9
+ local rate = tonumber(ARGV[2])
10
+ local capacity = tonumber(ARGV[3])
11
+
12
+ -- 获取当前时间(Redis服务器时间)
13
+ local now = redis.call('TIME')
14
+ local nowInSeconds = tonumber(now[1])
15
+
16
+ -- 获取桶状态
17
+ local bucket = redis.call('HMGET', key, 'tokens', 'last_time')
18
+ local tokens = tonumber(bucket[1])
19
+ local last_time = tonumber(bucket[2])
20
+
21
+ -- 初始化桶(首次请求或过期)
22
+ if not tokens or not last_time then
23
+ tokens = capacity
24
+ last_time = nowInSeconds
25
+ else
26
+ -- 计算新增令牌
27
+ local elapsed = nowInSeconds - last_time
28
+ local add_tokens = elapsed * rate
29
+ tokens = math.min(capacity, tokens + add_tokens)
30
+ last_time = nowInSeconds
31
+ end
32
+
33
+ -- 判断是否允许请求
34
+ local allowed = false
35
+ if tokens >= requested then
36
+ tokens = tokens - requested
37
+ allowed = true
38
+ end
39
+
40
+ ---- 更新桶状态并设置过期时间
41
+ redis.call('HMSET', key, 'tokens', tokens, 'last_time', last_time)
42
+ --redis.call('EXPIRE', key, math.ceil(capacity / rate) + 60) -- 适当延长过期时间
43
+
44
+ return allowed and 1 or 0
common/model.go ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ package common
2
+
3
+ import "strings"
4
+
5
+ var (
6
+ // OpenAIResponseOnlyModels is a list of models that are only available for OpenAI responses.
7
+ OpenAIResponseOnlyModels = []string{
8
+ "o3-pro",
9
+ "o3-deep-research",
10
+ "o4-mini-deep-research",
11
+ }
12
+ ImageGenerationModels = []string{
13
+ "dall-e-3",
14
+ "dall-e-2",
15
+ "gpt-image-1",
16
+ "prefix:imagen-",
17
+ "flux-",
18
+ "flux.1-",
19
+ }
20
+ )
21
+
22
+ func IsOpenAIResponseOnlyModel(modelName string) bool {
23
+ for _, m := range OpenAIResponseOnlyModels {
24
+ if strings.Contains(modelName, m) {
25
+ return true
26
+ }
27
+ }
28
+ return false
29
+ }
30
+
31
+ func IsImageGenerationModel(modelName string) bool {
32
+ modelName = strings.ToLower(modelName)
33
+ for _, m := range ImageGenerationModels {
34
+ if strings.Contains(modelName, m) {
35
+ return true
36
+ }
37
+ if strings.HasPrefix(m, "prefix:") && strings.HasPrefix(modelName, strings.TrimPrefix(m, "prefix:")) {
38
+ return true
39
+ }
40
+ }
41
+ return false
42
+ }