/* Copyright (C) 2025 QuantumNous This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . For commercial licensing, please contact support@quantumnous.com */ import React, { useEffect, useState, useRef } from 'react'; import { Banner, Button, Form, Space, Spin, RadioGroup, Radio, Table, Modal, Input, Divider, } from '@douyinfe/semi-ui'; import { IconPlus, IconEdit, IconDelete, IconSearch, IconSaveStroked, } from '@douyinfe/semi-icons'; import { compareObjects, API, showError, showSuccess, showWarning, verifyJSON, } from '../../../helpers'; import { useTranslation } from 'react-i18next'; export default function SettingsChats(props) { const { t } = useTranslation(); const [loading, setLoading] = useState(false); const [inputs, setInputs] = useState({ Chats: '[]', }); const refForm = useRef(); const [inputsRow, setInputsRow] = useState(inputs); const [editMode, setEditMode] = useState('visual'); const [chatConfigs, setChatConfigs] = useState([]); const [modalVisible, setModalVisible] = useState(false); const [editingConfig, setEditingConfig] = useState(null); const [isEdit, setIsEdit] = useState(false); const [searchText, setSearchText] = useState(''); const modalFormRef = useRef(); const jsonToConfigs = (jsonString) => { try { const configs = JSON.parse(jsonString); return Array.isArray(configs) ? configs.map((config, index) => ({ id: index, name: Object.keys(config)[0] || '', url: Object.values(config)[0] || '', })) : []; } catch (error) { console.error('JSON parse error:', error); return []; } }; const configsToJson = (configs) => { const jsonArray = configs.map((config) => ({ [config.name]: config.url, })); return JSON.stringify(jsonArray, null, 2); }; const syncJsonToConfigs = () => { const configs = jsonToConfigs(inputs.Chats); setChatConfigs(configs); }; const syncConfigsToJson = (configs) => { const jsonString = configsToJson(configs); setInputs((prev) => ({ ...prev, Chats: jsonString, })); if (refForm.current && editMode === 'json') { refForm.current.setValues({ Chats: jsonString }); } }; async function onSubmit() { try { console.log('Starting validation...'); await refForm.current .validate() .then(() => { console.log('Validation passed'); const updateArray = compareObjects(inputs, inputsRow); if (!updateArray.length) return showWarning(t('你似乎并没有修改什么')); const requestQueue = updateArray.map((item) => { let value = ''; if (typeof inputs[item.key] === 'boolean') { value = String(inputs[item.key]); } else { value = inputs[item.key]; } return API.put('/api/option/', { key: item.key, value, }); }); setLoading(true); Promise.all(requestQueue) .then((res) => { if (requestQueue.length === 1) { if (res.includes(undefined)) return; } else if (requestQueue.length > 1) { if (res.includes(undefined)) return showError(t('部分保存失败,请重试')); } showSuccess(t('保存成功')); props.refresh(); }) .catch(() => { showError(t('保存失败,请重试')); }) .finally(() => { setLoading(false); }); }) .catch((error) => { console.error('Validation failed:', error); showError(t('请检查输入')); }); } catch (error) { showError(t('请检查输入')); console.error(error); } } useEffect(() => { const currentInputs = {}; for (let key in props.options) { if (Object.keys(inputs).includes(key)) { if (key === 'Chats') { const obj = JSON.parse(props.options[key]); currentInputs[key] = JSON.stringify(obj, null, 2); } else { currentInputs[key] = props.options[key]; } } } setInputs(currentInputs); setInputsRow(structuredClone(currentInputs)); if (refForm.current) { refForm.current.setValues(currentInputs); } // 同步到可视化配置 const configs = jsonToConfigs(currentInputs.Chats || '[]'); setChatConfigs(configs); }, [props.options]); useEffect(() => { if (editMode === 'visual') { syncJsonToConfigs(); } }, [inputs.Chats, editMode]); useEffect(() => { if (refForm.current && editMode === 'json') { refForm.current.setValues(inputs); } }, [editMode, inputs]); const handleAddConfig = () => { setEditingConfig({ name: '', url: '' }); setIsEdit(false); setModalVisible(true); setTimeout(() => { if (modalFormRef.current) { modalFormRef.current.setValues({ name: '', url: '' }); } }, 100); }; const handleEditConfig = (config) => { setEditingConfig({ ...config }); setIsEdit(true); setModalVisible(true); setTimeout(() => { if (modalFormRef.current) { modalFormRef.current.setValues(config); } }, 100); }; const handleDeleteConfig = (id) => { const newConfigs = chatConfigs.filter((config) => config.id !== id); setChatConfigs(newConfigs); syncConfigsToJson(newConfigs); showSuccess(t('删除成功')); }; const handleModalOk = () => { if (modalFormRef.current) { modalFormRef.current .validate() .then((values) => { // 检查名称是否重复 const isDuplicate = chatConfigs.some( (config) => config.name === values.name && (!isEdit || config.id !== editingConfig.id), ); if (isDuplicate) { showError(t('聊天应用名称已存在,请使用其他名称')); return; } if (isEdit) { const newConfigs = chatConfigs.map((config) => config.id === editingConfig.id ? { ...editingConfig, name: values.name, url: values.url } : config, ); setChatConfigs(newConfigs); syncConfigsToJson(newConfigs); } else { const maxId = chatConfigs.length > 0 ? Math.max(...chatConfigs.map((c) => c.id)) : -1; const newConfig = { id: maxId + 1, name: values.name, url: values.url, }; const newConfigs = [...chatConfigs, newConfig]; setChatConfigs(newConfigs); syncConfigsToJson(newConfigs); } setModalVisible(false); setEditingConfig(null); showSuccess(isEdit ? t('编辑成功') : t('添加成功')); }) .catch((error) => { console.error('Modal form validation error:', error); }); } }; const handleModalCancel = () => { setModalVisible(false); setEditingConfig(null); }; const filteredConfigs = chatConfigs.filter( (config) => !searchText || config.name.toLowerCase().includes(searchText.toLowerCase()), ); const highlightKeywords = (text) => { if (!text) return text; const parts = text.split(/(\{address\}|\{key\})/g); return parts.map((part, index) => { if (part === '{address}') { return ( {part} ); } else if (part === '{key}') { return ( {part} ); } return part; }); }; const columns = [ { title: t('聊天应用名称'), dataIndex: 'name', key: 'name', render: (text) => text || t('未命名'), }, { title: t('URL链接'), dataIndex: 'url', key: 'url', render: (text) => (
{highlightKeywords(text)}
), }, { title: t('操作'), key: 'action', render: (_, record) => ( ), }, ]; return (
{t('编辑模式')}: { const newMode = e.target.value; setEditMode(newMode); // 确保模式切换时数据正确同步 setTimeout(() => { if (newMode === 'json' && refForm.current) { refForm.current.setValues(inputs); } }, 100); }} > {t('可视化编辑')} {t('JSON编辑')}
{editMode === 'visual' ? (
} placeholder={t('搜索聊天应用名称')} value={searchText} onChange={(value) => setSearchText(value)} style={{ width: 250 }} showClear /> t('共 {{total}} 项,当前显示 {{start}}-{{end}} 项', { total, start: range[0], end: range[1], }), }} /> ) : ( (refForm.current = formAPI)} > { return verifyJSON(value); }, message: t('不是合法的 JSON 字符串'), }, ]} onChange={(value) => setInputs({ ...inputs, Chats: value, }) } /> )} {editMode === 'json' && ( )}
(modalFormRef.current = api)}>
); }