import PropTypes from "prop-types"; import { useState, useEffect } from "react"; import { CHANNEL_OPTIONS } from "constants/ChannelConstants"; import { useTheme } from "@mui/material/styles"; import { API } from "utils/api"; import { showError, showSuccess } from "utils/common"; import { Dialog, DialogTitle, DialogContent, DialogActions, TextField, Button, Divider, Select, MenuItem, FormControl, InputLabel, OutlinedInput, ButtonGroup, Container, Autocomplete, FormHelperText, Checkbox } from "@mui/material"; import { Formik } from "formik"; import * as Yup from "yup"; import { defaultConfig, typeConfig } from "../type/Config"; //typeConfig import { createFilterOptions } from "@mui/material/Autocomplete"; import CheckBoxOutlineBlankIcon from '@mui/icons-material/CheckBoxOutlineBlank'; import CheckBoxIcon from '@mui/icons-material/CheckBox'; const icon = ; const checkedIcon = ; const filter = createFilterOptions(); const validationSchema = Yup.object().shape({ is_edit: Yup.boolean(), name: Yup.string().required("名称 不能为空"), type: Yup.number().required("渠道 不能为空"), key: Yup.string().when("is_edit", { is: false, then: Yup.string().required("密钥 不能为空"), }), other: Yup.string(), models: Yup.array().min(1, "模型 不能为空"), groups: Yup.array().min(1, "用户组 不能为空"), base_url: Yup.string().when("type", { is: (value) => [3, 8].includes(value), then: Yup.string().required("渠道API地址 不能为空"), // base_url 是必需的 otherwise: Yup.string(), // 在其他情况下,base_url 可以是任意字符串 }), model_mapping: Yup.string().test( "is-json", "必须是有效的JSON字符串", function (value) { try { if (value === "" || value === null || value === undefined) { return true; } const parsedValue = JSON.parse(value); if (typeof parsedValue === "object") { return true; } } catch (e) { return false; } return false; } ), }); const EditModal = ({ open, channelId, onCancel, onOk }) => { const theme = useTheme(); // const [loading, setLoading] = useState(false); const [initialInput, setInitialInput] = useState(defaultConfig.input); const [inputLabel, setInputLabel] = useState(defaultConfig.inputLabel); // const [inputPrompt, setInputPrompt] = useState(defaultConfig.prompt); const [groupOptions, setGroupOptions] = useState([]); const [modelOptions, setModelOptions] = useState([]); const initChannel = (typeValue) => { if (typeConfig[typeValue]?.inputLabel) { setInputLabel({ ...defaultConfig.inputLabel, ...typeConfig[typeValue].inputLabel, }); } else { setInputLabel(defaultConfig.inputLabel); } if (typeConfig[typeValue]?.prompt) { setInputPrompt({ ...defaultConfig.prompt, ...typeConfig[typeValue].prompt, }); } else { setInputPrompt(defaultConfig.prompt); } return typeConfig[typeValue]?.input; }; const handleTypeChange = (setFieldValue, typeValue, values) => { const newInput = initChannel(typeValue); if (newInput) { Object.keys(newInput).forEach((key) => { if ( (!Array.isArray(values[key]) && values[key] !== null && values[key] !== undefined && values[key] !== "") || (Array.isArray(values[key]) && values[key].length > 0) ) { return; } if (key === "models") { setFieldValue(key, initialModel(newInput[key])); return; } setFieldValue(key, newInput[key]); }); } }; const basicModels = (channelType) => { let modelGroup = typeConfig[channelType]?.modelGroup || defaultConfig.modelGroup; // 循环 modelOptions,找到 modelGroup 对应的模型 let modelList = []; modelOptions.forEach((model) => { if (model.group === modelGroup) { modelList.push(model); } }); return modelList; }; const fetchGroups = async () => { try { let res = await API.get(`/api/group/`); setGroupOptions(res.data.data); } catch (error) { showError(error.message); } }; const fetchModels = async () => { try { let res = await API.get(`/api/channel/models`); const { data } = res.data; data.forEach(item => { if (!item.owned_by) { item.owned_by = "未知"; } }); // 先对data排序 data.sort((a, b) => { const ownedByComparison = a.owned_by.localeCompare(b.owned_by); if (ownedByComparison === 0) { return a.id.localeCompare(b.id); } return ownedByComparison; }); setModelOptions( data.map((model) => { return { id: model.id, group: model.owned_by, }; }) ); } catch (error) { showError(error.message); } }; const submit = async (values, { setErrors, setStatus, setSubmitting }) => { setSubmitting(true); if (values.base_url && values.base_url.endsWith("/")) { values.base_url = values.base_url.slice(0, values.base_url.length - 1); } if (values.type === 3 && values.other === "") { values.other = "2023-09-01-preview"; } if (values.type === 18 && values.other === "") { values.other = "v2.1"; } let res; const modelsStr = values.models.map((model) => model.id).join(","); values.group = values.groups.join(","); if (channelId) { res = await API.put(`/api/channel/`, { ...values, id: parseInt(channelId), models: modelsStr, }); } else { res = await API.post(`/api/channel/`, { ...values, models: modelsStr }); } const { success, message } = res.data; if (success) { if (channelId) { showSuccess("渠道更新成功!"); } else { showSuccess("渠道创建成功!"); } setSubmitting(false); setStatus({ success: true }); onOk(true); } else { setStatus({ success: false }); showError(message); setErrors({ submit: message }); } }; function initialModel(channelModel) { if (!channelModel) { return []; } // 如果 channelModel 是一个字符串 if (typeof channelModel === "string") { channelModel = channelModel.split(","); } let modelList = channelModel.map((model) => { const modelOption = modelOptions.find((option) => option.id === model); if (modelOption) { return modelOption; } return { id: model, group: "自定义:点击或回车输入" }; }); return modelList; } const loadChannel = async () => { let res = await API.get(`/api/channel/${channelId}`); const { success, message, data } = res.data; if (success) { if (data.models === "") { data.models = []; } else { data.models = initialModel(data.models); } 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 ); } data.base_url = data.base_url ?? ''; data.is_edit = true; initChannel(data.type); setInitialInput(data); } else { showError(message); } }; useEffect(() => { fetchGroups().then(); fetchModels().then(); }, []); useEffect(() => { if (channelId) { loadChannel().then(); } else { initChannel(1); setInitialInput({ ...defaultConfig.input, is_edit: false }); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [channelId]); return ( {channelId ? "编辑渠道" : "新建渠道"} {({ errors, handleBlur, handleChange, handleSubmit, isSubmitting, touched, values, setFieldValue, }) => (
{inputLabel.type} {touched.type && errors.type ? ( {errors.type} ) : ( {" "} {inputPrompt.type}{" "} )} {inputLabel.name} {touched.name && errors.name ? ( {errors.name} ) : ( {" "} {inputPrompt.name}{" "} )} {inputLabel.base_url} {touched.base_url && errors.base_url ? ( {errors.base_url} ) : ( {" "} {inputPrompt.base_url}{" "} )} {inputPrompt.other && ( {inputLabel.other} {touched.other && errors.other ? ( {errors.other} ) : ( {" "} {inputPrompt.other}{" "} )} )} { const event = { target: { name: "groups", value: value, }, }; handleChange(event); }} onBlur={handleBlur} filterSelectedOptions renderInput={(params) => ( )} aria-describedby="helper-text-channel-groups-label" /> {errors.groups ? ( {errors.groups} ) : ( {" "} {inputPrompt.groups}{" "} )} { const event = { target: { name: "models", value: value.map((item) => typeof item === "string" ? { id: item, group: "自定义:点击或回车输入" } : item ), }, }; handleChange(event); }} onBlur={handleBlur} // filterSelectedOptions disableCloseOnSelect renderInput={(params) => ( )} groupBy={(option) => option.group} getOptionLabel={(option) => { if (typeof option === "string") { return option; } if (option.inputValue) { return option.inputValue; } return option.id; }} filterOptions={(options, params) => { const filtered = filter(options, params); const { inputValue } = params; const isExisting = options.some( (option) => inputValue === option.id ); if (inputValue !== "" && !isExisting) { filtered.push({ id: inputValue, group: "自定义:点击或回车输入", }); } return filtered; }} renderOption={(props, option, { selected }) => (
  • {option.id}
  • )} /> {errors.models ? ( {errors.models} ) : ( {" "} {inputPrompt.models}{" "} )}
    {inputLabel.key} {touched.key && errors.key ? ( {errors.key} ) : ( {" "} {inputPrompt.key}{" "} )} {/* {inputLabel.model_mapping} */} {touched.model_mapping && errors.model_mapping ? ( {errors.model_mapping} ) : ( {" "} {inputPrompt.model_mapping}{" "} )}
    )}
    ); }; export default EditModal; EditModal.propTypes = { open: PropTypes.bool, channelId: PropTypes.number, onCancel: PropTypes.func, onOk: PropTypes.func, };