Compare commits

...

6 Commits

9 changed files with 144 additions and 63 deletions

View File

@@ -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: ''
**预期结果**
**相关截图**
如果没有的话,请删除此节。

View File

@@ -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.

View File

@@ -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 查询或提问。

View File

@@ -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 可能会被无视或直接关闭
**功能描述**

View File

@@ -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**

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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 {

View File

@@ -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),
);
}