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 号) [//]: # (方框内删除已有的空格,填 x 号)
+ [ ] 我已确认目前没有类似 issue + [ ] 我已确认目前没有类似 issue
+ [ ] 我已确认我已升级到最新版本 + [ ] 我已完整查看过文档 https://docs.newapi.ai/ 和项目 README尤其是常见问题部分
+ [ ] 我已完整查看过项目 README尤其是常见问题部分 + [ ] 我未删除此模板中的任何引导内容或小节标题,并会按要求完整填写
+ [ ] 我理解并愿意跟进此 issue协助测试和提供反馈 + [ ] 我理解项目维护者精力有限,不遵循模板要求的 issue 可能会被无视或直接关闭
+ [ ] 我理解并认可上述内容,并理解项目维护者精力有限,**不遵循规则的 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) [//]: # (Remove the space in the box and fill with an x)
+ [ ] I have confirmed there are no similar issues currently + [ ] I have confirmed there are no similar issues
+ [ ] I have confirmed I have upgraded to the latest version + [ ] I have thoroughly read the docs at https://docs.newapi.ai/ and the project README, especially the FAQ section
+ [ ] I have thoroughly read 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 and am willing to follow up on this issue, assist with testing and provide feedback + [ ] I understand that maintainers have limited time and issues that do not follow this template may be ignored or closed directly
+ [ ] 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**
**Issue Description** **Issue Description**
@@ -23,4 +32,3 @@ assignees: ''
**Expected Result** **Expected Result**
**Related Screenshots** **Related Screenshots**
If none, please delete this section.

View File

@@ -1,5 +1,8 @@
blank_issues_enabled: false blank_issues_enabled: false
contact_links: contact_links:
- name: 项目群聊 - name: 使用文档 / Documentation
url: https://private-user-images.githubusercontent.com/61247483/283011625-de536a8a-0161-47a7-a0a2-66ef6de81266.jpeg?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJnaXRodWIuY29tIiwiYXVkIjoicmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbSIsImtleSI6ImtleTEiLCJleHAiOjE3MDIyMjQzOTAsIm5iZiI6MTcwMjIyNDA5MCwicGF0aCI6Ii82MTI0NzQ4My8yODMwMTE2MjUtZGU1MzZhOGEtMDE2MS00N2E3LWEwYTItNjZlZjZkZTgxMjY2LmpwZWc_WC1BbXotQWxnb3JpdGhtPUFXUzQtSE1BQy1TSEEyNTYmWC1BbXotQ3JlZGVudGlhbD1BS0lBSVdOSllBWDRDU1ZFSDUzQSUyRjIwMjMxMjEwJTJGdXMtZWFzdC0xJTJGczMlMkZhd3M0X3JlcXVlc3QmWC1BbXotRGF0ZT0yMDIzMTIxMFQxNjAxMzBaJlgtQW16LUV4cGlyZXM9MzAwJlgtQW16LVNpZ25hdHVyZT02MGIxYmM3ZDQyYzBkOTA2ZTYyYmVmMzQ1NjY4NjM1YjY0NTUzNTM5NjE1NDZkYTIzODdhYTk4ZjZjODJmYzY2JlgtQW16LVNpZ25lZEhlYWRlcnM9aG9zdCZhY3Rvcl9pZD0wJmtleV9pZD0wJnJlcG9faWQ9MCJ9.TJ8CTfOSwR0-CHS1KLfomqgL0e4YH1luy8lSLrkv5Zg url: https://docs.newapi.ai/
about: QQ 群629454374 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 号) [//]: # (方框内删除已有的空格,填 x 号)
+ [ ] 我已确认目前没有类似 issue + [ ] 我已确认目前没有类似 issue
+ [ ] 我已确认我已升级到最新版本 + [ ] 我已完整查看过文档 https://docs.newapi.ai/ 和项目 README已确定现有版本无法满足需求
+ [ ] 我已完整查看过项目 README已确定现有版本无法满足需求 + [ ] 我未删除此模板中的任何引导内容或小节标题,并会按要求完整填写
+ [ ] 我理解并愿意跟进此 issue协助测试和提供反馈 + [ ] 我理解项目维护者精力有限,不遵循模板要求的 issue 可能会被无视或直接关闭
+ [ ] 我理解并认可上述内容,并理解项目维护者精力有限,**不遵循规则的 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) [//]: # (Remove the space in the box and fill with an x)
+ [ ] I have confirmed there are no similar issues currently + [ ] I have confirmed there are no similar issues
+ [ ] I have confirmed I have upgraded to the latest version + [ ] 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 thoroughly read 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 and am willing to follow up on this issue, assist with testing and provide feedback + [ ] I understand that maintainers have limited time and issues that do not follow this template may be ignored or closed directly
+ [ ] 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**
**Feature Description** **Feature Description**
**Use Case** **Use Case**

View File

@@ -225,8 +225,12 @@ func (a *Adaptor) SetupRequestHeader(c *gin.Context, header *http.Header, info *
} }
} }
if info.ChannelType == constant.ChannelTypeOpenRouter { if info.ChannelType == constant.ChannelTypeOpenRouter {
header.Set("HTTP-Referer", "https://www.newapi.ai") if header.Get("HTTP-Referer") == "" {
header.Set("X-OpenRouter-Title", "New API") header.Set("HTTP-Referer", "https://www.newapi.ai")
}
if header.Get("X-OpenRouter-Title") == "" {
header.Set("X-OpenRouter-Title", "New API")
}
} }
return nil return nil
} }

View File

@@ -214,7 +214,7 @@ func registerMjRouterGroup(relayMjRouter *gin.RouterGroup) {
relayMjRouter.POST("/submit/blend", controller.RelayMidjourney) relayMjRouter.POST("/submit/blend", controller.RelayMidjourney)
relayMjRouter.POST("/submit/edits", controller.RelayMidjourney) relayMjRouter.POST("/submit/edits", controller.RelayMidjourney)
relayMjRouter.POST("/submit/video", 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/fetch", controller.RelayMidjourney)
relayMjRouter.GET("/task/:id/image-seed", controller.RelayMidjourney) relayMjRouter.GET("/task/:id/image-seed", controller.RelayMidjourney)
relayMjRouter.POST("/task/list-by-condition", 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 useUserGroupRatio = isValidGroupRatio(user_group_ratio);
const ratioLabel = useUserGroupRatio const ratioLabel = useUserGroupRatio
? i18next.t('专属倍率') ? i18next.t('专属倍率')
: i18next.t('分组倍率'); : i18next.t('分组');
const effectiveRatio = useUserGroupRatio ? user_group_ratio : groupRatio; const effectiveRatio = useUserGroupRatio ? user_group_ratio : groupRatio;
return { return {

View File

@@ -59,6 +59,11 @@ const formatNumber = (value) => {
return parseFloat(num.toFixed(12)).toString(); return parseFloat(num.toFixed(12)).toString();
}; };
const toNormalizedNumber = (value) => {
const formatted = formatNumber(value);
return formatted === '' ? null : Number(formatted);
};
const parseOptionJSON = (rawValue) => { const parseOptionJSON = (rawValue) => {
if (!rawValue || rawValue.trim() === '') { if (!rawValue || rawValue.trim() === '') {
return {}; return {};
@@ -123,7 +128,11 @@ const buildModelState = (name, sourceMaps) => {
lockedCompletionRatio: completionRatioMeta.ratio, lockedCompletionRatio: completionRatioMeta.ratio,
completionPrice: completionPrice:
inputPriceNumber !== null && inputPriceNumber !== null &&
hasValue(completionRatioMeta.locked ? completionRatioMeta.ratio : completionRatio) hasValue(
completionRatioMeta.locked
? completionRatioMeta.ratio
: completionRatio,
)
? formatNumber( ? formatNumber(
inputPriceNumber * inputPriceNumber *
Number( Number(
@@ -192,7 +201,9 @@ export const getModelWarnings = (model, t) => {
].some(hasValue); ].some(hasValue);
if (model.hasConflict) { if (model.hasConflict) {
warnings.push(t('当前模型同时存在按次价格和倍率配置,保存时会按当前计费方式覆盖。')); warnings.push(
t('当前模型同时存在按次价格和倍率配置,保存时会按当前计费方式覆盖。'),
);
} }
if ( if (
@@ -207,11 +218,17 @@ export const getModelWarnings = (model, t) => {
].some(hasValue) ].some(hasValue)
) { ) {
warnings.push( 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('按量计费下需要先填写输入价格,才能保存其它价格项。')); warnings.push(t('按量计费下需要先填写输入价格,才能保存其它价格项。'));
} }
@@ -249,7 +266,8 @@ export const buildSummaryText = (model, t) => {
}; };
export const buildOptionalFieldToggles = (model) => ({ export const buildOptionalFieldToggles = (model) => ({
completionPrice: model.completionRatioLocked || hasValue(model.completionPrice), completionPrice:
model.completionRatioLocked || hasValue(model.completionPrice),
cachePrice: hasValue(model.cachePrice), cachePrice: hasValue(model.cachePrice),
createCachePrice: hasValue(model.createCachePrice), createCachePrice: hasValue(model.createCachePrice),
imagePrice: hasValue(model.imagePrice), imagePrice: hasValue(model.imagePrice),
@@ -271,7 +289,7 @@ const serializeModel = (model, t) => {
if (model.billingMode === 'per-request') { if (model.billingMode === 'per-request') {
if (hasValue(model.fixedPrice)) { if (hasValue(model.fixedPrice)) {
result.ModelPrice = Number(model.fixedPrice); result.ModelPrice = toNormalizedNumber(model.fixedPrice);
} }
return result; return result;
} }
@@ -296,57 +314,68 @@ const serializeModel = (model, t) => {
if (inputPrice === null) { if (inputPrice === null) {
if (hasDependentPrice) { if (hasDependentPrice) {
throw new Error( throw new Error(
t('模型 {{name}} 缺少输入价格,无法计算补全/缓存/图片/音频价格对应的倍率', { t(
name: model.name, '模型 {{name}} 缺少输入价格,无法计算补全/缓存/图片/音频价格对应的倍率',
}), {
name: model.name,
},
),
); );
} }
if (hasValue(model.rawRatios.modelRatio)) { if (hasValue(model.rawRatios.modelRatio)) {
result.ModelRatio = Number(model.rawRatios.modelRatio); result.ModelRatio = toNormalizedNumber(model.rawRatios.modelRatio);
} }
if (hasValue(model.rawRatios.completionRatio)) { if (hasValue(model.rawRatios.completionRatio)) {
result.CompletionRatio = Number(model.rawRatios.completionRatio); result.CompletionRatio = toNormalizedNumber(
model.rawRatios.completionRatio,
);
} }
if (hasValue(model.rawRatios.cacheRatio)) { if (hasValue(model.rawRatios.cacheRatio)) {
result.CacheRatio = Number(model.rawRatios.cacheRatio); result.CacheRatio = toNormalizedNumber(model.rawRatios.cacheRatio);
} }
if (hasValue(model.rawRatios.createCacheRatio)) { if (hasValue(model.rawRatios.createCacheRatio)) {
result.CreateCacheRatio = Number(model.rawRatios.createCacheRatio); result.CreateCacheRatio = toNormalizedNumber(
model.rawRatios.createCacheRatio,
);
} }
if (hasValue(model.rawRatios.imageRatio)) { if (hasValue(model.rawRatios.imageRatio)) {
result.ImageRatio = Number(model.rawRatios.imageRatio); result.ImageRatio = toNormalizedNumber(model.rawRatios.imageRatio);
} }
if (hasValue(model.rawRatios.audioRatio)) { if (hasValue(model.rawRatios.audioRatio)) {
result.AudioRatio = Number(model.rawRatios.audioRatio); result.AudioRatio = toNormalizedNumber(model.rawRatios.audioRatio);
} }
if (hasValue(model.rawRatios.audioCompletionRatio)) { if (hasValue(model.rawRatios.audioCompletionRatio)) {
result.AudioCompletionRatio = Number(model.rawRatios.audioCompletionRatio); result.AudioCompletionRatio = toNormalizedNumber(
model.rawRatios.audioCompletionRatio,
);
} }
return result; return result;
} }
result.ModelRatio = inputPrice / 2; result.ModelRatio = toNormalizedNumber(inputPrice / 2);
if (!model.completionRatioLocked && completionPrice !== null) { if (!model.completionRatioLocked && completionPrice !== null) {
result.CompletionRatio = completionPrice / inputPrice; result.CompletionRatio = toNormalizedNumber(completionPrice / inputPrice);
} else if ( } else if (
model.completionRatioLocked && model.completionRatioLocked &&
hasValue(model.rawRatios.completionRatio) hasValue(model.rawRatios.completionRatio)
) { ) {
result.CompletionRatio = Number(model.rawRatios.completionRatio); result.CompletionRatio = toNormalizedNumber(
model.rawRatios.completionRatio,
);
} }
if (cachePrice !== null) { if (cachePrice !== null) {
result.CacheRatio = cachePrice / inputPrice; result.CacheRatio = toNormalizedNumber(cachePrice / inputPrice);
} }
if (createCachePrice !== null) { if (createCachePrice !== null) {
result.CreateCacheRatio = createCachePrice / inputPrice; result.CreateCacheRatio = toNormalizedNumber(createCachePrice / inputPrice);
} }
if (imagePrice !== null) { if (imagePrice !== null) {
result.ImageRatio = imagePrice / inputPrice; result.ImageRatio = toNormalizedNumber(imagePrice / inputPrice);
} }
if (audioInputPrice !== null) { if (audioInputPrice !== null) {
result.AudioRatio = audioInputPrice / inputPrice; result.AudioRatio = toNormalizedNumber(audioInputPrice / inputPrice);
} }
if (audioOutputPrice !== null) { if (audioOutputPrice !== null) {
if (audioInputPrice === null || audioInputPrice === 0) { if (audioInputPrice === null || audioInputPrice === 0) {
@@ -356,7 +385,9 @@ const serializeModel = (model, t) => {
}), }),
); );
} }
result.AudioCompletionRatio = audioOutputPrice / audioInputPrice; result.AudioCompletionRatio = toNormalizedNumber(
audioOutputPrice / audioInputPrice,
);
} }
return result; return result;
@@ -455,7 +486,8 @@ export const buildPreviewRows = (model, t) => {
{ {
key: 'CacheRatio', key: 'CacheRatio',
label: 'CacheRatio', label: 'CacheRatio',
value: cachePrice !== null ? formatNumber(cachePrice / inputPrice) : t('空'), value:
cachePrice !== null ? formatNumber(cachePrice / inputPrice) : t('空'),
}, },
{ {
key: 'CreateCacheRatio', key: 'CreateCacheRatio',
@@ -468,7 +500,8 @@ export const buildPreviewRows = (model, t) => {
{ {
key: 'ImageRatio', key: 'ImageRatio',
label: 'ImageRatio', label: 'ImageRatio',
value: imagePrice !== null ? formatNumber(imagePrice / inputPrice) : t('空'), value:
imagePrice !== null ? formatNumber(imagePrice / inputPrice) : t('空'),
}, },
{ {
key: 'AudioRatio', key: 'AudioRatio',
@@ -482,7 +515,9 @@ export const buildPreviewRows = (model, t) => {
key: 'AudioCompletionRatio', key: 'AudioCompletionRatio',
label: 'AudioCompletionRatio', label: 'AudioCompletionRatio',
value: value:
audioOutputPrice !== null && audioInputPrice !== null && audioInputPrice !== 0 audioOutputPrice !== null &&
audioInputPrice !== null &&
audioInputPrice !== 0
? formatNumber(audioOutputPrice / audioInputPrice) ? formatNumber(audioOutputPrice / audioInputPrice)
: t('空'), : t('空'),
}, },
@@ -585,7 +620,8 @@ export function useModelPricingEditorState({
}, [currentPage, filteredModels]); }, [currentPage, filteredModels]);
const selectedModel = useMemo( const selectedModel = useMemo(
() => visibleModels.find((model) => model.name === selectedModelName) || null, () =>
visibleModels.find((model) => model.name === selectedModelName) || null,
[selectedModelName, visibleModels], [selectedModelName, visibleModels],
); );
@@ -605,7 +641,9 @@ export function useModelPricingEditorState({
useEffect(() => { useEffect(() => {
setSelectedModelNames((previous) => setSelectedModelNames((previous) =>
previous.filter((name) => visibleModels.some((model) => model.name === name)), previous.filter((name) =>
visibleModels.some((model) => model.name === name),
),
); );
}, [visibleModels]); }, [visibleModels]);
@@ -779,7 +817,9 @@ export function useModelPricingEditorState({
delete next[name]; delete next[name];
return next; return next;
}); });
setSelectedModelNames((previous) => previous.filter((item) => item !== name)); setSelectedModelNames((previous) =>
previous.filter((item) => item !== name),
);
if (selectedModelName === name) { if (selectedModelName === name) {
setSelectedModelName(nextModels[0]?.name || ''); setSelectedModelName(nextModels[0]?.name || '');
} }
@@ -823,7 +863,8 @@ export function useModelPricingEditorState({
hasValue(nextModel.lockedCompletionRatio) hasValue(nextModel.lockedCompletionRatio)
) { ) {
nextModel.completionPrice = formatNumber( nextModel.completionPrice = formatNumber(
Number(nextModel.inputPrice) * Number(nextModel.lockedCompletionRatio), Number(nextModel.inputPrice) *
Number(nextModel.lockedCompletionRatio),
); );
} }