import React, { useEffect, useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import { API, isMobile, showError, showInfo, showSuccess, verifyJSON, } from '../../helpers'; import { CHANNEL_OPTIONS } from '../../constants'; import { SideSheet, Space, Spin, Button, Input, Typography, Select, TextArea, Checkbox, Banner, Modal, ImagePreview, Card, Tag, } from '@douyinfe/semi-ui'; import { getChannelModels } from '../../helpers'; import { IconSave, IconClose, IconServer, IconSetting, IconCode, IconGlobe, } from '@douyinfe/semi-icons'; const { Text, Title } = Typography; const MODEL_MAPPING_EXAMPLE = { 'gpt-3.5-turbo': 'gpt-3.5-turbo-0125', }; const STATUS_CODE_MAPPING_EXAMPLE = { 400: '500', }; const REGION_EXAMPLE = { default: 'us-central1', 'claude-3-5-sonnet-20240620': 'europe-west1', }; function type2secretPrompt(type) { // inputs.type === 15 ? '按照如下格式输入:APIKey|SecretKey' : (inputs.type === 18 ? '按照如下格式输入:APPID|APISecret|APIKey' : '请输入渠道对应的鉴权密钥') switch (type) { case 15: return '按照如下格式输入:APIKey|SecretKey'; case 18: return '按照如下格式输入:APPID|APISecret|APIKey'; case 22: return '按照如下格式输入:APIKey-AppId,例如:fastgpt-0sp2gtvfdgyi4k30jwlgwf1i-64f335d84283f05518e9e041'; case 23: return '按照如下格式输入:AppId|SecretId|SecretKey'; case 33: return '按照如下格式输入:Ak|Sk|Region'; default: return '请输入渠道对应的鉴权密钥'; } } const EditChannel = (props) => { const { t } = useTranslation(); const navigate = useNavigate(); const channelId = props.editingChannel.id; const isEdit = channelId !== undefined; const [loading, setLoading] = useState(isEdit); const handleCancel = () => { props.handleClose(); }; const originInputs = { name: '', type: 1, key: '', openai_organization: '', max_input_tokens: 0, base_url: '', other: '', model_mapping: '', status_code_mapping: '', models: [], auto_ban: 1, test_model: '', groups: ['default'], priority: 0, weight: 0, tag: '', }; const [batch, setBatch] = useState(false); const [autoBan, setAutoBan] = useState(true); // const [autoBan, setAutoBan] = useState(true); const [inputs, setInputs] = useState(originInputs); const [originModelOptions, setOriginModelOptions] = useState([]); const [modelOptions, setModelOptions] = useState([]); const [groupOptions, setGroupOptions] = useState([]); const [basicModels, setBasicModels] = useState([]); const [fullModels, setFullModels] = useState([]); const [customModel, setCustomModel] = useState(''); const [modalImageUrl, setModalImageUrl] = useState(''); const [isModalOpenurl, setIsModalOpenurl] = useState(false); const handleInputChange = (name, value) => { if (name === 'base_url' && value.endsWith('/v1')) { Modal.confirm({ title: '警告', content: '不需要在末尾加/v1,New API会自动处理,添加后可能导致请求失败,是否继续?', onOk: () => { setInputs((inputs) => ({ ...inputs, [name]: value })); }, }); return; } setInputs((inputs) => ({ ...inputs, [name]: value })); if (name === 'type') { let localModels = []; switch (value) { case 2: localModels = [ 'mj_imagine', 'mj_variation', 'mj_reroll', 'mj_blend', 'mj_upscale', 'mj_describe', 'mj_uploads', ]; break; case 5: localModels = [ 'swap_face', 'mj_imagine', 'mj_variation', 'mj_reroll', 'mj_blend', 'mj_upscale', 'mj_describe', 'mj_zoom', 'mj_shorten', 'mj_modal', 'mj_inpaint', 'mj_custom_zoom', 'mj_high_variation', 'mj_low_variation', 'mj_pan', 'mj_uploads', ]; break; case 36: localModels = ['suno_music', 'suno_lyrics']; break; default: localModels = getChannelModels(value); break; } if (inputs.models.length === 0) { setInputs((inputs) => ({ ...inputs, models: localModels })); } setBasicModels(localModels); } //setAutoBan }; const loadChannel = async () => { setLoading(true); let res = await API.get(`/api/channel/${channelId}`); if (res === undefined) { return; } const { success, message, data } = res.data; if (success) { if (data.models === '') { data.models = []; } else { data.models = data.models.split(','); } if (data.group === '') { data.groups = []; } else { data.groups = data.group.split(','); } if (data.model_mapping !== '') { data.model_mapping = JSON.stringify( JSON.parse(data.model_mapping), null, 2, ); } setInputs(data); if (data.auto_ban === 0) { setAutoBan(false); } else { setAutoBan(true); } setBasicModels(getChannelModels(data.type)); // console.log(data); } else { showError(message); } setLoading(false); }; const fetchUpstreamModelList = async (name) => { // if (inputs['type'] !== 1) { // showError(t('仅支持 OpenAI 接口格式')); // return; // } setLoading(true); const models = inputs['models'] || []; let err = false; if (isEdit) { // 如果是编辑模式,使用已有的channel id获取模型列表 const res = await API.get('/api/channel/fetch_models/' + channelId); if (res.data && res.data?.success) { models.push(...res.data.data); } else { err = true; } } else { // 如果是新建模式,通过后端代理获取模型列表 if (!inputs?.['key']) { showError(t('请填写密钥')); err = true; } else { try { const res = await API.post('/api/channel/fetch_models', { base_url: inputs['base_url'], type: inputs['type'], key: inputs['key'], }); if (res.data && res.data.success) { models.push(...res.data.data); } else { err = true; } } catch (error) { console.error('Error fetching models:', error); err = true; } } } if (!err) { handleInputChange(name, Array.from(new Set(models))); showSuccess(t('获取模型列表成功')); } else { showError(t('获取模型列表失败')); } setLoading(false); }; const fetchModels = async () => { try { let res = await API.get(`/api/channel/models`); let localModelOptions = res.data.data.map((model) => ({ label: model.id, value: model.id, })); setOriginModelOptions(localModelOptions); setFullModels(res.data.data.map((model) => model.id)); setBasicModels( res.data.data .filter((model) => { return model.id.startsWith('gpt-') || model.id.startsWith('text-'); }) .map((model) => model.id), ); } catch (error) { showError(error.message); } }; const fetchGroups = async () => { try { let res = await API.get(`/api/group/`); if (res === undefined) { return; } setGroupOptions( res.data.data.map((group) => ({ label: group, value: group, })), ); } catch (error) { showError(error.message); } }; useEffect(() => { let localModelOptions = [...originModelOptions]; inputs.models.forEach((model) => { if (!localModelOptions.find((option) => option.label === model)) { localModelOptions.push({ label: model, value: model, }); } }); setModelOptions(localModelOptions); }, [originModelOptions, inputs.models]); useEffect(() => { fetchModels().then(); fetchGroups().then(); if (isEdit) { loadChannel().then(() => { }); } else { setInputs(originInputs); let localModels = getChannelModels(inputs.type); setBasicModels(localModels); setInputs((inputs) => ({ ...inputs, models: localModels })); } }, [props.editingChannel.id]); const submit = async () => { if (!isEdit && (inputs.name === '' || inputs.key === '')) { showInfo(t('请填写渠道名称和渠道密钥!')); return; } if (inputs.models.length === 0) { showInfo(t('请至少选择一个模型!')); return; } if (inputs.model_mapping !== '' && !verifyJSON(inputs.model_mapping)) { showInfo(t('模型映射必须是合法的 JSON 格式!')); return; } let localInputs = { ...inputs }; if (localInputs.base_url && localInputs.base_url.endsWith('/')) { localInputs.base_url = localInputs.base_url.slice( 0, localInputs.base_url.length - 1, ); } if (localInputs.type === 18 && localInputs.other === '') { localInputs.other = 'v2.1'; } let res; if (!Array.isArray(localInputs.models)) { showError(t('提交失败,请勿重复提交!')); handleCancel(); return; } localInputs.auto_ban = autoBan ? 1 : 0; localInputs.models = localInputs.models.join(','); localInputs.group = localInputs.groups.join(','); if (isEdit) { res = await API.put(`/api/channel/`, { ...localInputs, id: parseInt(channelId), }); } else { res = await API.post(`/api/channel/`, localInputs); } const { success, message } = res.data; if (success) { if (isEdit) { showSuccess(t('渠道更新成功!')); } else { showSuccess(t('渠道创建成功!')); setInputs(originInputs); } props.refresh(); props.handleClose(); } else { showError(message); } }; const addCustomModels = () => { if (customModel.trim() === '') return; const modelArray = customModel.split(',').map((model) => model.trim()); let localModels = [...inputs.models]; let localModelOptions = [...modelOptions]; let hasError = false; modelArray.forEach((model) => { if (model && !localModels.includes(model)) { localModels.push(model); localModelOptions.push({ key: model, text: model, value: model, }); } else if (model) { showError(t('某些模型已存在!')); hasError = true; } }); if (hasError) return; setModelOptions(localModelOptions); setCustomModel(''); handleInputChange('models', localModels); }; const handleAddPrefix = () => { const channelName = inputs.name; const currentModels = inputs.models; // This is an array of strings if (!channelName || !currentModels || currentModels.length === 0) { showError(t('请先填写渠道名称并选择模型!')); return; } const prefixedModels = currentModels.map( (model) => `${channelName}/${model}`, ); const mapping = {}; prefixedModels.forEach((prefixedModel, index) => { mapping[prefixedModel] = currentModels[index]; }); const modelMappingJson = JSON.stringify(mapping, null, 2); // Pretty print JSON handleInputChange('models', prefixedModels); handleInputChange('model_mapping', modelMappingJson); showSuccess(t('模型前缀添加成功!')); }; return ( <> {isEdit ? t('编辑') : t('新建')} {isEdit ? t('更新渠道信息') : t('创建新的渠道')} } headerStyle={{ borderBottom: '1px solid var(--semi-color-border)', padding: '24px' }} bodyStyle={{ backgroundColor: 'var(--semi-color-bg-0)', padding: '0' }} visible={props.visible} width={isMobile() ? '100%' : 600} footer={
} closeIcon={null} onCancel={() => handleCancel()} >
{t('基本信息')}
{t('渠道的基本配置信息')}
{t('类型')} { handleInputChange('name', value); }} value={inputs.name} autoComplete='new-password' size="large" className="!rounded-lg" />
{t('密钥')} {batch ? (