mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-03-30 02:25:00 +00:00
feat(web): add custom-model create hint and i18n translations
This commit is contained in:
@@ -191,6 +191,7 @@ const EditChannelModal = (props) => {
|
|||||||
const [fullModels, setFullModels] = useState([]);
|
const [fullModels, setFullModels] = useState([]);
|
||||||
const [modelGroups, setModelGroups] = useState([]);
|
const [modelGroups, setModelGroups] = useState([]);
|
||||||
const [customModel, setCustomModel] = useState('');
|
const [customModel, setCustomModel] = useState('');
|
||||||
|
const [modelSearchValue, setModelSearchValue] = useState('');
|
||||||
const [modalImageUrl, setModalImageUrl] = useState('');
|
const [modalImageUrl, setModalImageUrl] = useState('');
|
||||||
const [isModalOpenurl, setIsModalOpenurl] = useState(false);
|
const [isModalOpenurl, setIsModalOpenurl] = useState(false);
|
||||||
const [modelModalVisible, setModelModalVisible] = useState(false);
|
const [modelModalVisible, setModelModalVisible] = useState(false);
|
||||||
@@ -231,6 +232,25 @@ const EditChannelModal = (props) => {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}, [inputs.model_mapping]);
|
}, [inputs.model_mapping]);
|
||||||
|
const modelSearchMatchedCount = useMemo(() => {
|
||||||
|
const keyword = modelSearchValue.trim();
|
||||||
|
if (!keyword) {
|
||||||
|
return modelOptions.length;
|
||||||
|
}
|
||||||
|
return modelOptions.reduce(
|
||||||
|
(count, option) => count + (selectFilter(keyword, option) ? 1 : 0),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
}, [modelOptions, modelSearchValue]);
|
||||||
|
const modelSearchHintText = useMemo(() => {
|
||||||
|
const keyword = modelSearchValue.trim();
|
||||||
|
if (!keyword || modelSearchMatchedCount !== 0) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return t('未匹配到模型,按回车键可将「{{name}}」作为自定义模型名添加', {
|
||||||
|
name: keyword,
|
||||||
|
});
|
||||||
|
}, [modelSearchMatchedCount, modelSearchValue, t]);
|
||||||
const [isIonetChannel, setIsIonetChannel] = useState(false);
|
const [isIonetChannel, setIsIonetChannel] = useState(false);
|
||||||
const [ionetMetadata, setIonetMetadata] = useState(null);
|
const [ionetMetadata, setIonetMetadata] = useState(null);
|
||||||
const [codexOAuthModalVisible, setCodexOAuthModalVisible] = useState(false);
|
const [codexOAuthModalVisible, setCodexOAuthModalVisible] = useState(false);
|
||||||
@@ -1019,6 +1039,7 @@ const EditChannelModal = (props) => {
|
|||||||
}, [inputs]);
|
}, [inputs]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
setModelSearchValue('');
|
||||||
if (props.visible) {
|
if (props.visible) {
|
||||||
if (isEdit) {
|
if (isEdit) {
|
||||||
loadChannel();
|
loadChannel();
|
||||||
@@ -1073,6 +1094,7 @@ const EditChannelModal = (props) => {
|
|||||||
// 重置豆包隐藏入口状态
|
// 重置豆包隐藏入口状态
|
||||||
setDoubaoApiEditUnlocked(false);
|
setDoubaoApiEditUnlocked(false);
|
||||||
doubaoApiClickCountRef.current = 0;
|
doubaoApiClickCountRef.current = 0;
|
||||||
|
setModelSearchValue('');
|
||||||
// 清空表单中的key_mode字段
|
// 清空表单中的key_mode字段
|
||||||
if (formApiRef.current) {
|
if (formApiRef.current) {
|
||||||
formApiRef.current.setValue('key_mode', undefined);
|
formApiRef.current.setValue('key_mode', undefined);
|
||||||
@@ -2815,9 +2837,18 @@ const EditChannelModal = (props) => {
|
|||||||
rules={[{ required: true, message: t('请选择模型') }]}
|
rules={[{ required: true, message: t('请选择模型') }]}
|
||||||
multiple
|
multiple
|
||||||
filter={selectFilter}
|
filter={selectFilter}
|
||||||
|
allowCreate
|
||||||
autoClearSearchValue={false}
|
autoClearSearchValue={false}
|
||||||
searchPosition='dropdown'
|
searchPosition='dropdown'
|
||||||
optionList={modelOptions}
|
optionList={modelOptions}
|
||||||
|
onSearch={(value) => setModelSearchValue(value)}
|
||||||
|
innerBottomSlot={
|
||||||
|
modelSearchHintText ? (
|
||||||
|
<Text className='px-3 py-2 block text-xs !text-semi-color-text-2'>
|
||||||
|
{modelSearchHintText}
|
||||||
|
</Text>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
onChange={(value) => handleInputChange('models', value)}
|
onChange={(value) => handleInputChange('models', value)}
|
||||||
renderSelectedItem={(optionNode) => {
|
renderSelectedItem={(optionNode) => {
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||||||
For commercial licensing, please contact support@quantumnous.com
|
For commercial licensing, please contact support@quantumnous.com
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { useState, useEffect, useRef } from 'react';
|
import React, { useState, useEffect, useRef, useMemo } from 'react';
|
||||||
import {
|
import {
|
||||||
API,
|
API,
|
||||||
showError,
|
showError,
|
||||||
@@ -64,6 +64,7 @@ const EditTagModal = (props) => {
|
|||||||
const [modelOptions, setModelOptions] = useState([]);
|
const [modelOptions, setModelOptions] = useState([]);
|
||||||
const [groupOptions, setGroupOptions] = useState([]);
|
const [groupOptions, setGroupOptions] = useState([]);
|
||||||
const [customModel, setCustomModel] = useState('');
|
const [customModel, setCustomModel] = useState('');
|
||||||
|
const [modelSearchValue, setModelSearchValue] = useState('');
|
||||||
const originInputs = {
|
const originInputs = {
|
||||||
tag: '',
|
tag: '',
|
||||||
new_tag: null,
|
new_tag: null,
|
||||||
@@ -74,6 +75,25 @@ const EditTagModal = (props) => {
|
|||||||
header_override: null,
|
header_override: null,
|
||||||
};
|
};
|
||||||
const [inputs, setInputs] = useState(originInputs);
|
const [inputs, setInputs] = useState(originInputs);
|
||||||
|
const modelSearchMatchedCount = useMemo(() => {
|
||||||
|
const keyword = modelSearchValue.trim();
|
||||||
|
if (!keyword) {
|
||||||
|
return modelOptions.length;
|
||||||
|
}
|
||||||
|
return modelOptions.reduce(
|
||||||
|
(count, option) => count + (selectFilter(keyword, option) ? 1 : 0),
|
||||||
|
0,
|
||||||
|
);
|
||||||
|
}, [modelOptions, modelSearchValue]);
|
||||||
|
const modelSearchHintText = useMemo(() => {
|
||||||
|
const keyword = modelSearchValue.trim();
|
||||||
|
if (!keyword || modelSearchMatchedCount !== 0) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return t('未匹配到模型,按回车键可将「{{name}}」作为自定义模型名添加', {
|
||||||
|
name: keyword,
|
||||||
|
});
|
||||||
|
}, [modelSearchMatchedCount, modelSearchValue, t]);
|
||||||
const formApiRef = useRef(null);
|
const formApiRef = useRef(null);
|
||||||
const getInitValues = () => ({ ...originInputs });
|
const getInitValues = () => ({ ...originInputs });
|
||||||
|
|
||||||
@@ -292,6 +312,7 @@ const EditTagModal = (props) => {
|
|||||||
fetchModels().then();
|
fetchModels().then();
|
||||||
fetchGroups().then();
|
fetchGroups().then();
|
||||||
fetchTagModels().then();
|
fetchTagModels().then();
|
||||||
|
setModelSearchValue('');
|
||||||
if (formApiRef.current) {
|
if (formApiRef.current) {
|
||||||
formApiRef.current.setValues({
|
formApiRef.current.setValues({
|
||||||
...getInitValues(),
|
...getInitValues(),
|
||||||
@@ -461,9 +482,18 @@ const EditTagModal = (props) => {
|
|||||||
placeholder={t('请选择该渠道所支持的模型,留空则不更改')}
|
placeholder={t('请选择该渠道所支持的模型,留空则不更改')}
|
||||||
multiple
|
multiple
|
||||||
filter={selectFilter}
|
filter={selectFilter}
|
||||||
|
allowCreate
|
||||||
autoClearSearchValue={false}
|
autoClearSearchValue={false}
|
||||||
searchPosition='dropdown'
|
searchPosition='dropdown'
|
||||||
optionList={modelOptions}
|
optionList={modelOptions}
|
||||||
|
onSearch={(value) => setModelSearchValue(value)}
|
||||||
|
innerBottomSlot={
|
||||||
|
modelSearchHintText ? (
|
||||||
|
<Text className='px-3 py-2 block text-xs !text-semi-color-text-2'>
|
||||||
|
{modelSearchHintText}
|
||||||
|
</Text>
|
||||||
|
) : null
|
||||||
|
}
|
||||||
style={{ width: '100%' }}
|
style={{ width: '100%' }}
|
||||||
onChange={(value) => handleInputChange('models', value)}
|
onChange={(value) => handleInputChange('models', value)}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -2835,6 +2835,7 @@
|
|||||||
"缓存写": "Cache Write",
|
"缓存写": "Cache Write",
|
||||||
"写": "Write",
|
"写": "Write",
|
||||||
"根据 Anthropic 协定,/v1/messages 的输入 tokens 仅统计非缓存输入,不包含缓存读取与缓存写入 tokens。": "Per Anthropic conventions, /v1/messages input tokens count only non-cached input and exclude cache read/write tokens.",
|
"根据 Anthropic 协定,/v1/messages 的输入 tokens 仅统计非缓存输入,不包含缓存读取与缓存写入 tokens。": "Per Anthropic conventions, /v1/messages input tokens count only non-cached input and exclude cache read/write tokens.",
|
||||||
"设计版本": "b80c3466cb6feafeb3990c7820e10e50"
|
"设计版本": "b80c3466cb6feafeb3990c7820e10e50",
|
||||||
|
"未匹配到模型,按回车键可将「{{name}}」作为自定义模型名添加": "No matching models. Press Enter to add \"{{name}}\" as a custom model name."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2737,6 +2737,7 @@
|
|||||||
"缓存写": "Écriture cache",
|
"缓存写": "Écriture cache",
|
||||||
"写": "Écriture",
|
"写": "Écriture",
|
||||||
"根据 Anthropic 协定,/v1/messages 的输入 tokens 仅统计非缓存输入,不包含缓存读取与缓存写入 tokens。": "Selon la convention Anthropic, les tokens d'entrée de /v1/messages ne comptent que les entrées non mises en cache et excluent les tokens de lecture/écriture du cache.",
|
"根据 Anthropic 协定,/v1/messages 的输入 tokens 仅统计非缓存输入,不包含缓存读取与缓存写入 tokens。": "Selon la convention Anthropic, les tokens d'entrée de /v1/messages ne comptent que les entrées non mises en cache et excluent les tokens de lecture/écriture du cache.",
|
||||||
"设计版本": "b80c3466cb6feafeb3990c7820e10e50"
|
"设计版本": "b80c3466cb6feafeb3990c7820e10e50",
|
||||||
|
"未匹配到模型,按回车键可将「{{name}}」作为自定义模型名添加": "Aucun modèle correspondant. Appuyez sur Entrée pour ajouter «{{name}}» comme nom de modèle personnalisé."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2720,6 +2720,7 @@
|
|||||||
"缓存写": "キャッシュ書込",
|
"缓存写": "キャッシュ書込",
|
||||||
"写": "書込",
|
"写": "書込",
|
||||||
"根据 Anthropic 协定,/v1/messages 的输入 tokens 仅统计非缓存输入,不包含缓存读取与缓存写入 tokens。": "Anthropic の仕様により、/v1/messages の入力 tokens は非キャッシュ入力のみを集計し、キャッシュ読み取り/書き込み tokens は含みません。",
|
"根据 Anthropic 协定,/v1/messages 的输入 tokens 仅统计非缓存输入,不包含缓存读取与缓存写入 tokens。": "Anthropic の仕様により、/v1/messages の入力 tokens は非キャッシュ入力のみを集計し、キャッシュ読み取り/書き込み tokens は含みません。",
|
||||||
"设计版本": "b80c3466cb6feafeb3990c7820e10e50"
|
"设计版本": "b80c3466cb6feafeb3990c7820e10e50",
|
||||||
|
"未匹配到模型,按回车键可将「{{name}}」作为自定义模型名添加": "一致するモデルが見つかりません。Enterキーで「{{name}}」をカスタムモデル名として追加できます。"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2750,6 +2750,7 @@
|
|||||||
"缓存写": "Запись в кэш",
|
"缓存写": "Запись в кэш",
|
||||||
"写": "Запись",
|
"写": "Запись",
|
||||||
"根据 Anthropic 协定,/v1/messages 的输入 tokens 仅统计非缓存输入,不包含缓存读取与缓存写入 tokens。": "Согласно соглашению Anthropic, входные токены /v1/messages учитывают только некэшированный ввод и не включают токены чтения/записи кэша.",
|
"根据 Anthropic 协定,/v1/messages 的输入 tokens 仅统计非缓存输入,不包含缓存读取与缓存写入 tokens。": "Согласно соглашению Anthropic, входные токены /v1/messages учитывают только некэшированный ввод и не включают токены чтения/записи кэша.",
|
||||||
"设计版本": "b80c3466cb6feafeb3990c7820e10e50"
|
"设计版本": "b80c3466cb6feafeb3990c7820e10e50",
|
||||||
|
"未匹配到模型,按回车键可将「{{name}}」作为自定义模型名添加": "Совпадающих моделей не найдено. Нажмите Enter, чтобы добавить «{{name}}» как пользовательское имя модели."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3296,6 +3296,7 @@
|
|||||||
"缓存写": "Ghi bộ nhớ đệm",
|
"缓存写": "Ghi bộ nhớ đệm",
|
||||||
"写": "Ghi",
|
"写": "Ghi",
|
||||||
"根据 Anthropic 协定,/v1/messages 的输入 tokens 仅统计非缓存输入,不包含缓存读取与缓存写入 tokens。": "Theo quy ước của Anthropic, input tokens của /v1/messages chỉ tính phần đầu vào không dùng cache và không bao gồm tokens đọc/ghi cache.",
|
"根据 Anthropic 协定,/v1/messages 的输入 tokens 仅统计非缓存输入,不包含缓存读取与缓存写入 tokens。": "Theo quy ước của Anthropic, input tokens của /v1/messages chỉ tính phần đầu vào không dùng cache và không bao gồm tokens đọc/ghi cache.",
|
||||||
"设计版本": "b80c3466cb6feafeb3990c7820e10e50"
|
"设计版本": "b80c3466cb6feafeb3990c7820e10e50",
|
||||||
|
"未匹配到模型,按回车键可将「{{name}}」作为自定义模型名添加": "Không tìm thấy mô hình khớp. Nhấn Enter để thêm \"{{name}}\" làm tên mô hình tùy chỉnh."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2812,6 +2812,7 @@
|
|||||||
"缓存读": "缓存读",
|
"缓存读": "缓存读",
|
||||||
"缓存写": "缓存写",
|
"缓存写": "缓存写",
|
||||||
"写": "写",
|
"写": "写",
|
||||||
"根据 Anthropic 协定,/v1/messages 的输入 tokens 仅统计非缓存输入,不包含缓存读取与缓存写入 tokens。": "根据 Anthropic 协定,/v1/messages 的输入 tokens 仅统计非缓存输入,不包含缓存读取与缓存写入 tokens。"
|
"根据 Anthropic 协定,/v1/messages 的输入 tokens 仅统计非缓存输入,不包含缓存读取与缓存写入 tokens。": "根据 Anthropic 协定,/v1/messages 的输入 tokens 仅统计非缓存输入,不包含缓存读取与缓存写入 tokens。",
|
||||||
|
"未匹配到模型,按回车键可将「{{name}}」作为自定义模型名添加": "未匹配到模型,按回车键可将「{{name}}」作为自定义模型名添加"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2805,6 +2805,7 @@
|
|||||||
"填写服务器地址后自动生成:": "填寫伺服器位址後自動生成:",
|
"填写服务器地址后自动生成:": "填寫伺服器位址後自動生成:",
|
||||||
"自动生成:": "自動生成:",
|
"自动生成:": "自動生成:",
|
||||||
"请先填写服务器地址,以自动生成完整的端点 URL": "請先填寫伺服器位址,以自動生成完整的端點 URL",
|
"请先填写服务器地址,以自动生成完整的端点 URL": "請先填寫伺服器位址,以自動生成完整的端點 URL",
|
||||||
"端点 URL 必须是完整地址(以 http:// 或 https:// 开头)": "端點 URL 必須是完整位址(以 http:// 或 https:// 開頭)"
|
"端点 URL 必须是完整地址(以 http:// 或 https:// 开头)": "端點 URL 必須是完整位址(以 http:// 或 https:// 開頭)",
|
||||||
|
"未匹配到模型,按回车键可将「{{name}}」作为自定义模型名添加": "未匹配到模型,按下 Enter 鍵可將「{{name}}」作為自訂模型名稱新增"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user