mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-03-30 12:51:58 +00:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ca4ff503e | ||
|
|
dea9353842 | ||
|
|
4deb1eb70f | ||
|
|
092ee07e94 | ||
|
|
4e1b05e987 | ||
|
|
3bd1a167c9 |
20
.github/ISSUE_TEMPLATE/bug_report.md
vendored
20
.github/ISSUE_TEMPLATE/bug_report.md
vendored
@@ -7,14 +7,23 @@ assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**例行检查**
|
||||
## 提交前必读(请勿删除本节)
|
||||
|
||||
- 文档:https://docs.newapi.ai/
|
||||
- 使用问题先看或先问:https://deepwiki.com/QuantumNous/new-api
|
||||
- 警告:删除本模板、删除小节标题或随意清空内容的 issue,可能会被直接关闭;重复恶意提交者可能会被 block。
|
||||
|
||||
**您当前的 newapi 版本**
|
||||
|
||||
请填写,例如:`v1.0.0`
|
||||
|
||||
**提交确认**
|
||||
|
||||
[//]: # (方框内删除已有的空格,填 x 号)
|
||||
+ [ ] 我已确认目前没有类似 issue
|
||||
+ [ ] 我已确认我已升级到最新版本
|
||||
+ [ ] 我已完整查看过项目 README,尤其是常见问题部分
|
||||
+ [ ] 我理解并愿意跟进此 issue,协助测试和提供反馈
|
||||
+ [ ] 我理解并认可上述内容,并理解项目维护者精力有限,**不遵循规则的 issue 可能会被无视或直接关闭**
|
||||
+ [ ] 我已完整查看过文档 https://docs.newapi.ai/ 和项目 README,尤其是常见问题部分
|
||||
+ [ ] 我未删除此模板中的任何引导内容或小节标题,并会按要求完整填写
|
||||
+ [ ] 我理解项目维护者精力有限,不遵循模板要求的 issue 可能会被无视或直接关闭
|
||||
|
||||
**问题描述**
|
||||
|
||||
@@ -23,4 +32,3 @@ assignees: ''
|
||||
**预期结果**
|
||||
|
||||
**相关截图**
|
||||
如果没有的话,请删除此节。
|
||||
22
.github/ISSUE_TEMPLATE/bug_report_en.md
vendored
22
.github/ISSUE_TEMPLATE/bug_report_en.md
vendored
@@ -7,14 +7,23 @@ assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Routine Checks**
|
||||
## Read This First (Do Not Remove This Section)
|
||||
|
||||
- Docs: https://docs.newapi.ai/
|
||||
- Usage questions first: https://deepwiki.com/QuantumNous/new-api
|
||||
- Warning: issues with this template removed, section headings deleted, or content cleared may be closed directly. Repeated abusive submissions may result in a block.
|
||||
|
||||
**Your current newapi version**
|
||||
|
||||
Please fill this in, for example: `v1.0.0`
|
||||
|
||||
**Submission Checks**
|
||||
|
||||
[//]: # (Remove the space in the box and fill with an x)
|
||||
+ [ ] I have confirmed there are no similar issues currently
|
||||
+ [ ] I have confirmed I have upgraded to the latest version
|
||||
+ [ ] I have thoroughly read the project README, especially the FAQ section
|
||||
+ [ ] I understand and am willing to follow up on this issue, assist with testing and provide feedback
|
||||
+ [ ] 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**
|
||||
+ [ ] I have confirmed there are no similar issues
|
||||
+ [ ] I have thoroughly read the docs at https://docs.newapi.ai/ and the project README, especially the FAQ section
|
||||
+ [ ] I have not removed any guidance or section headings from this template and will complete it as requested
|
||||
+ [ ] I understand that maintainers have limited time and issues that do not follow this template may be ignored or closed directly
|
||||
|
||||
**Issue Description**
|
||||
|
||||
@@ -23,4 +32,3 @@ assignees: ''
|
||||
**Expected Result**
|
||||
|
||||
**Related Screenshots**
|
||||
If none, please delete this section.
|
||||
9
.github/ISSUE_TEMPLATE/config.yml
vendored
9
.github/ISSUE_TEMPLATE/config.yml
vendored
@@ -1,5 +1,8 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: 项目群聊
|
||||
url: https://private-user-images.githubusercontent.com/61247483/283011625-de536a8a-0161-47a7-a0a2-66ef6de81266.jpeg?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTEiLCJleHAiOjE3MDIyMjQzOTAsIm5iZiI6MTcwMjIyNDA5MCwicGF0aCI6Ii82MTI0NzQ4My8yODMwMTE2MjUtZGU1MzZhOGEtMDE2MS00N2E3LWEwYTItNjZlZjZkZTgxMjY2LmpwZWc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBSVdOSllBWDRDU1ZFSDUzQSUyRjIwMjMxMjEwJTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDIzMTIxMFQxNjAxMzBaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT02MGIxYmM3ZDQyYzBkOTA2ZTYyYmVmMzQ1NjY4NjM1YjY0NTUzNTM5NjE1NDZkYTIzODdhYTk4ZjZjODJmYzY2JlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCZhY3Rvcl9pZD0wJmtleV9pZD0wJnJlcG9faWQ9MCJ9.TJ8CTfOSwR0-CHS1KLfomqgL0e4YH1luy8lSLrkv5Zg
|
||||
about: QQ 群:629454374
|
||||
- name: 使用文档 / Documentation
|
||||
url: https://docs.newapi.ai/
|
||||
about: 提交 issue 前请先查阅文档,确认现有说明无法解决你的问题。
|
||||
- name: 使用问题 / Usage Questions
|
||||
url: https://deepwiki.com/QuantumNous/new-api
|
||||
about: 使用、配置、接入等问题请优先在 DeepWiki 查询或提问。
|
||||
|
||||
19
.github/ISSUE_TEMPLATE/feature_request.md
vendored
19
.github/ISSUE_TEMPLATE/feature_request.md
vendored
@@ -7,14 +7,23 @@ assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**例行检查**
|
||||
## 提交前必读(请勿删除本节)
|
||||
|
||||
- 文档:https://docs.newapi.ai/
|
||||
- 使用问题先看或先问:https://deepwiki.com/QuantumNous/new-api
|
||||
- 警告:删除本模板、删除小节标题或随意清空内容的 issue,可能会被直接关闭;重复恶意提交者可能会被 block。
|
||||
|
||||
**您当前的 newapi 版本**
|
||||
|
||||
请填写,例如:`v1.0.0`
|
||||
|
||||
**提交确认**
|
||||
|
||||
[//]: # (方框内删除已有的空格,填 x 号)
|
||||
+ [ ] 我已确认目前没有类似 issue
|
||||
+ [ ] 我已确认我已升级到最新版本
|
||||
+ [ ] 我已完整查看过项目 README,已确定现有版本无法满足需求
|
||||
+ [ ] 我理解并愿意跟进此 issue,协助测试和提供反馈
|
||||
+ [ ] 我理解并认可上述内容,并理解项目维护者精力有限,**不遵循规则的 issue 可能会被无视或直接关闭**
|
||||
+ [ ] 我已完整查看过文档 https://docs.newapi.ai/ 和项目 README,已确定现有版本无法满足需求
|
||||
+ [ ] 我未删除此模板中的任何引导内容或小节标题,并会按要求完整填写
|
||||
+ [ ] 我理解项目维护者精力有限,不遵循模板要求的 issue 可能会被无视或直接关闭
|
||||
|
||||
**功能描述**
|
||||
|
||||
|
||||
22
.github/ISSUE_TEMPLATE/feature_request_en.md
vendored
22
.github/ISSUE_TEMPLATE/feature_request_en.md
vendored
@@ -7,16 +7,24 @@ assignees: ''
|
||||
|
||||
---
|
||||
|
||||
**Routine Checks**
|
||||
## Read This First (Do Not Remove This Section)
|
||||
|
||||
- Docs: https://docs.newapi.ai/
|
||||
- Usage questions first: https://deepwiki.com/QuantumNous/new-api
|
||||
- Warning: issues with this template removed, section headings deleted, or content cleared may be closed directly. Repeated abusive submissions may result in a block.
|
||||
|
||||
**Your current newapi version**
|
||||
|
||||
Please fill this in, for example: `v1.0.0`
|
||||
|
||||
**Submission Checks**
|
||||
|
||||
[//]: # (Remove the space in the box and fill with an x)
|
||||
+ [ ] I have confirmed there are no similar issues currently
|
||||
+ [ ] I have confirmed I have upgraded to the latest version
|
||||
+ [ ] I have thoroughly read the project README and confirmed the current version cannot meet my needs
|
||||
+ [ ] I understand and am willing to follow up on this issue, assist with testing and provide feedback
|
||||
+ [ ] 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**
|
||||
+ [ ] I have confirmed there are no similar issues
|
||||
+ [ ] I have thoroughly read the docs at https://docs.newapi.ai/ and the project README, and confirmed the current version cannot meet my needs
|
||||
+ [ ] I have not removed any guidance or section headings from this template and will complete it as requested
|
||||
+ [ ] I understand that maintainers have limited time and issues that do not follow this template may be ignored or closed directly
|
||||
|
||||
**Feature Description**
|
||||
|
||||
**Use Case**
|
||||
|
||||
|
||||
@@ -225,8 +225,12 @@ func (a *Adaptor) SetupRequestHeader(c *gin.Context, header *http.Header, info *
|
||||
}
|
||||
}
|
||||
if info.ChannelType == constant.ChannelTypeOpenRouter {
|
||||
header.Set("HTTP-Referer", "https://www.newapi.ai")
|
||||
header.Set("X-OpenRouter-Title", "New API")
|
||||
if header.Get("HTTP-Referer") == "" {
|
||||
header.Set("HTTP-Referer", "https://www.newapi.ai")
|
||||
}
|
||||
if header.Get("X-OpenRouter-Title") == "" {
|
||||
header.Set("X-OpenRouter-Title", "New API")
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -214,7 +214,7 @@ func registerMjRouterGroup(relayMjRouter *gin.RouterGroup) {
|
||||
relayMjRouter.POST("/submit/blend", controller.RelayMidjourney)
|
||||
relayMjRouter.POST("/submit/edits", controller.RelayMidjourney)
|
||||
relayMjRouter.POST("/submit/video", controller.RelayMidjourney)
|
||||
relayMjRouter.POST("/notify", controller.RelayMidjourney)
|
||||
//relayMjRouter.POST("/notify", controller.RelayMidjourney)
|
||||
relayMjRouter.GET("/task/:id/fetch", controller.RelayMidjourney)
|
||||
relayMjRouter.GET("/task/:id/image-seed", controller.RelayMidjourney)
|
||||
relayMjRouter.POST("/task/list-by-condition", controller.RelayMidjourney)
|
||||
|
||||
2
web/src/helpers/render.jsx
vendored
2
web/src/helpers/render.jsx
vendored
@@ -1183,7 +1183,7 @@ function getEffectiveRatio(groupRatio, user_group_ratio) {
|
||||
const useUserGroupRatio = isValidGroupRatio(user_group_ratio);
|
||||
const ratioLabel = useUserGroupRatio
|
||||
? i18next.t('专属倍率')
|
||||
: i18next.t('分组倍率');
|
||||
: i18next.t('分组');
|
||||
const effectiveRatio = useUserGroupRatio ? user_group_ratio : groupRatio;
|
||||
|
||||
return {
|
||||
|
||||
@@ -59,6 +59,11 @@ const formatNumber = (value) => {
|
||||
return parseFloat(num.toFixed(12)).toString();
|
||||
};
|
||||
|
||||
const toNormalizedNumber = (value) => {
|
||||
const formatted = formatNumber(value);
|
||||
return formatted === '' ? null : Number(formatted);
|
||||
};
|
||||
|
||||
const parseOptionJSON = (rawValue) => {
|
||||
if (!rawValue || rawValue.trim() === '') {
|
||||
return {};
|
||||
@@ -123,7 +128,11 @@ const buildModelState = (name, sourceMaps) => {
|
||||
lockedCompletionRatio: completionRatioMeta.ratio,
|
||||
completionPrice:
|
||||
inputPriceNumber !== null &&
|
||||
hasValue(completionRatioMeta.locked ? completionRatioMeta.ratio : completionRatio)
|
||||
hasValue(
|
||||
completionRatioMeta.locked
|
||||
? completionRatioMeta.ratio
|
||||
: completionRatio,
|
||||
)
|
||||
? formatNumber(
|
||||
inputPriceNumber *
|
||||
Number(
|
||||
@@ -192,7 +201,9 @@ export const getModelWarnings = (model, t) => {
|
||||
].some(hasValue);
|
||||
|
||||
if (model.hasConflict) {
|
||||
warnings.push(t('当前模型同时存在按次价格和倍率配置,保存时会按当前计费方式覆盖。'));
|
||||
warnings.push(
|
||||
t('当前模型同时存在按次价格和倍率配置,保存时会按当前计费方式覆盖。'),
|
||||
);
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -207,11 +218,17 @@ export const getModelWarnings = (model, t) => {
|
||||
].some(hasValue)
|
||||
) {
|
||||
warnings.push(
|
||||
t('当前模型存在未显式设置输入倍率的扩展倍率;填写输入价格后会自动换算为价格字段。'),
|
||||
t(
|
||||
'当前模型存在未显式设置输入倍率的扩展倍率;填写输入价格后会自动换算为价格字段。',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (model.billingMode === 'per-token' && hasDerivedPricing && !hasValue(model.inputPrice)) {
|
||||
if (
|
||||
model.billingMode === 'per-token' &&
|
||||
hasDerivedPricing &&
|
||||
!hasValue(model.inputPrice)
|
||||
) {
|
||||
warnings.push(t('按量计费下需要先填写输入价格,才能保存其它价格项。'));
|
||||
}
|
||||
|
||||
@@ -249,7 +266,8 @@ export const buildSummaryText = (model, t) => {
|
||||
};
|
||||
|
||||
export const buildOptionalFieldToggles = (model) => ({
|
||||
completionPrice: model.completionRatioLocked || hasValue(model.completionPrice),
|
||||
completionPrice:
|
||||
model.completionRatioLocked || hasValue(model.completionPrice),
|
||||
cachePrice: hasValue(model.cachePrice),
|
||||
createCachePrice: hasValue(model.createCachePrice),
|
||||
imagePrice: hasValue(model.imagePrice),
|
||||
@@ -271,7 +289,7 @@ const serializeModel = (model, t) => {
|
||||
|
||||
if (model.billingMode === 'per-request') {
|
||||
if (hasValue(model.fixedPrice)) {
|
||||
result.ModelPrice = Number(model.fixedPrice);
|
||||
result.ModelPrice = toNormalizedNumber(model.fixedPrice);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
@@ -296,57 +314,68 @@ const serializeModel = (model, t) => {
|
||||
if (inputPrice === null) {
|
||||
if (hasDependentPrice) {
|
||||
throw new Error(
|
||||
t('模型 {{name}} 缺少输入价格,无法计算补全/缓存/图片/音频价格对应的倍率', {
|
||||
name: model.name,
|
||||
}),
|
||||
t(
|
||||
'模型 {{name}} 缺少输入价格,无法计算补全/缓存/图片/音频价格对应的倍率',
|
||||
{
|
||||
name: model.name,
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (hasValue(model.rawRatios.modelRatio)) {
|
||||
result.ModelRatio = Number(model.rawRatios.modelRatio);
|
||||
result.ModelRatio = toNormalizedNumber(model.rawRatios.modelRatio);
|
||||
}
|
||||
if (hasValue(model.rawRatios.completionRatio)) {
|
||||
result.CompletionRatio = Number(model.rawRatios.completionRatio);
|
||||
result.CompletionRatio = toNormalizedNumber(
|
||||
model.rawRatios.completionRatio,
|
||||
);
|
||||
}
|
||||
if (hasValue(model.rawRatios.cacheRatio)) {
|
||||
result.CacheRatio = Number(model.rawRatios.cacheRatio);
|
||||
result.CacheRatio = toNormalizedNumber(model.rawRatios.cacheRatio);
|
||||
}
|
||||
if (hasValue(model.rawRatios.createCacheRatio)) {
|
||||
result.CreateCacheRatio = Number(model.rawRatios.createCacheRatio);
|
||||
result.CreateCacheRatio = toNormalizedNumber(
|
||||
model.rawRatios.createCacheRatio,
|
||||
);
|
||||
}
|
||||
if (hasValue(model.rawRatios.imageRatio)) {
|
||||
result.ImageRatio = Number(model.rawRatios.imageRatio);
|
||||
result.ImageRatio = toNormalizedNumber(model.rawRatios.imageRatio);
|
||||
}
|
||||
if (hasValue(model.rawRatios.audioRatio)) {
|
||||
result.AudioRatio = Number(model.rawRatios.audioRatio);
|
||||
result.AudioRatio = toNormalizedNumber(model.rawRatios.audioRatio);
|
||||
}
|
||||
if (hasValue(model.rawRatios.audioCompletionRatio)) {
|
||||
result.AudioCompletionRatio = Number(model.rawRatios.audioCompletionRatio);
|
||||
result.AudioCompletionRatio = toNormalizedNumber(
|
||||
model.rawRatios.audioCompletionRatio,
|
||||
);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
result.ModelRatio = inputPrice / 2;
|
||||
result.ModelRatio = toNormalizedNumber(inputPrice / 2);
|
||||
|
||||
if (!model.completionRatioLocked && completionPrice !== null) {
|
||||
result.CompletionRatio = completionPrice / inputPrice;
|
||||
result.CompletionRatio = toNormalizedNumber(completionPrice / inputPrice);
|
||||
} else if (
|
||||
model.completionRatioLocked &&
|
||||
hasValue(model.rawRatios.completionRatio)
|
||||
) {
|
||||
result.CompletionRatio = Number(model.rawRatios.completionRatio);
|
||||
result.CompletionRatio = toNormalizedNumber(
|
||||
model.rawRatios.completionRatio,
|
||||
);
|
||||
}
|
||||
if (cachePrice !== null) {
|
||||
result.CacheRatio = cachePrice / inputPrice;
|
||||
result.CacheRatio = toNormalizedNumber(cachePrice / inputPrice);
|
||||
}
|
||||
if (createCachePrice !== null) {
|
||||
result.CreateCacheRatio = createCachePrice / inputPrice;
|
||||
result.CreateCacheRatio = toNormalizedNumber(createCachePrice / inputPrice);
|
||||
}
|
||||
if (imagePrice !== null) {
|
||||
result.ImageRatio = imagePrice / inputPrice;
|
||||
result.ImageRatio = toNormalizedNumber(imagePrice / inputPrice);
|
||||
}
|
||||
if (audioInputPrice !== null) {
|
||||
result.AudioRatio = audioInputPrice / inputPrice;
|
||||
result.AudioRatio = toNormalizedNumber(audioInputPrice / inputPrice);
|
||||
}
|
||||
if (audioOutputPrice !== null) {
|
||||
if (audioInputPrice === null || audioInputPrice === 0) {
|
||||
@@ -356,7 +385,9 @@ const serializeModel = (model, t) => {
|
||||
}),
|
||||
);
|
||||
}
|
||||
result.AudioCompletionRatio = audioOutputPrice / audioInputPrice;
|
||||
result.AudioCompletionRatio = toNormalizedNumber(
|
||||
audioOutputPrice / audioInputPrice,
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
@@ -455,7 +486,8 @@ export const buildPreviewRows = (model, t) => {
|
||||
{
|
||||
key: 'CacheRatio',
|
||||
label: 'CacheRatio',
|
||||
value: cachePrice !== null ? formatNumber(cachePrice / inputPrice) : t('空'),
|
||||
value:
|
||||
cachePrice !== null ? formatNumber(cachePrice / inputPrice) : t('空'),
|
||||
},
|
||||
{
|
||||
key: 'CreateCacheRatio',
|
||||
@@ -468,7 +500,8 @@ export const buildPreviewRows = (model, t) => {
|
||||
{
|
||||
key: 'ImageRatio',
|
||||
label: 'ImageRatio',
|
||||
value: imagePrice !== null ? formatNumber(imagePrice / inputPrice) : t('空'),
|
||||
value:
|
||||
imagePrice !== null ? formatNumber(imagePrice / inputPrice) : t('空'),
|
||||
},
|
||||
{
|
||||
key: 'AudioRatio',
|
||||
@@ -482,7 +515,9 @@ export const buildPreviewRows = (model, t) => {
|
||||
key: 'AudioCompletionRatio',
|
||||
label: 'AudioCompletionRatio',
|
||||
value:
|
||||
audioOutputPrice !== null && audioInputPrice !== null && audioInputPrice !== 0
|
||||
audioOutputPrice !== null &&
|
||||
audioInputPrice !== null &&
|
||||
audioInputPrice !== 0
|
||||
? formatNumber(audioOutputPrice / audioInputPrice)
|
||||
: t('空'),
|
||||
},
|
||||
@@ -585,7 +620,8 @@ export function useModelPricingEditorState({
|
||||
}, [currentPage, filteredModels]);
|
||||
|
||||
const selectedModel = useMemo(
|
||||
() => visibleModels.find((model) => model.name === selectedModelName) || null,
|
||||
() =>
|
||||
visibleModels.find((model) => model.name === selectedModelName) || null,
|
||||
[selectedModelName, visibleModels],
|
||||
);
|
||||
|
||||
@@ -605,7 +641,9 @@ export function useModelPricingEditorState({
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedModelNames((previous) =>
|
||||
previous.filter((name) => visibleModels.some((model) => model.name === name)),
|
||||
previous.filter((name) =>
|
||||
visibleModels.some((model) => model.name === name),
|
||||
),
|
||||
);
|
||||
}, [visibleModels]);
|
||||
|
||||
@@ -779,7 +817,9 @@ export function useModelPricingEditorState({
|
||||
delete next[name];
|
||||
return next;
|
||||
});
|
||||
setSelectedModelNames((previous) => previous.filter((item) => item !== name));
|
||||
setSelectedModelNames((previous) =>
|
||||
previous.filter((item) => item !== name),
|
||||
);
|
||||
if (selectedModelName === name) {
|
||||
setSelectedModelName(nextModels[0]?.name || '');
|
||||
}
|
||||
@@ -823,7 +863,8 @@ export function useModelPricingEditorState({
|
||||
hasValue(nextModel.lockedCompletionRatio)
|
||||
) {
|
||||
nextModel.completionPrice = formatNumber(
|
||||
Number(nextModel.inputPrice) * Number(nextModel.lockedCompletionRatio),
|
||||
Number(nextModel.inputPrice) *
|
||||
Number(nextModel.lockedCompletionRatio),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user