liuzhao521
commited on
Commit
·
4674012
0
Parent(s):
Deploy New API v0.9.25+ (commit b47cf4ef) to HuggingFace Spaces
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- .dockerignore +9 -0
- .env.example +76 -0
- .gitattributes +3 -0
- .github/FUNDING.yml +12 -0
- .github/ISSUE_TEMPLATE/bug_report.md +26 -0
- .github/ISSUE_TEMPLATE/bug_report_en.md +26 -0
- .github/ISSUE_TEMPLATE/config.yml +5 -0
- .github/ISSUE_TEMPLATE/feature_request.md +21 -0
- .github/ISSUE_TEMPLATE/feature_request_en.md +22 -0
- .github/PULL_REQUEST_TEMPLATE/pull_request_template.md +15 -0
- .github/workflows/docker-image-alpha.yml +151 -0
- .github/workflows/docker-image-arm64.yml +138 -0
- .github/workflows/electron-build.yml +141 -0
- .github/workflows/release.yml +142 -0
- .github/workflows/sync-to-gitee.yml +91 -0
- .gitignore +23 -0
- Dockerfile +44 -0
- Dockerfile.hf +44 -0
- LICENSE +103 -0
- README.en.md +447 -0
- README.fr.md +441 -0
- README.ja.md +450 -0
- README.md +459 -0
- VERSION +0 -0
- bin/migration_v0.2-v0.3.sql +6 -0
- bin/migration_v0.3-v0.4.sql +17 -0
- bin/time_test.sh +40 -0
- common/api_type.go +81 -0
- common/audio.go +296 -0
- common/constants.go +204 -0
- common/copy.go +19 -0
- common/crypto.go +32 -0
- common/custom-event.go +87 -0
- common/database.go +15 -0
- common/email-outlook-auth.go +40 -0
- common/email.go +93 -0
- common/embed-file-system.go +43 -0
- common/endpoint_defaults.go +33 -0
- common/endpoint_type.go +43 -0
- common/env.go +38 -0
- common/gin.go +234 -0
- common/go-channel.go +53 -0
- common/gopool.go +25 -0
- common/hash.go +34 -0
- common/init.go +144 -0
- common/ip.go +22 -0
- common/json.go +45 -0
- common/limiter/limiter.go +90 -0
- common/limiter/lua/rate_limit.lua +44 -0
- 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 |
+

|
| 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/) | [](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 |
+
[](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 |
+

|
| 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/) | [](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 |
+
[](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 |
+

|
| 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/) | [](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://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 |
+

|
| 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/) | [](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 |
+
[](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 |
+
}
|