| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271 |
- /*
- 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 <https://www.gnu.org/licenses/>.
- For commercial licensing, please contact support@quantumnous.com
- */
- import React, { useState, useEffect } from 'react';
- import {
- SideSheet,
- Button,
- Typography,
- Space,
- Tag,
- Popconfirm,
- Card,
- Avatar,
- Spin,
- Empty,
- } from '@douyinfe/semi-ui';
- import {
- IconPlus,
- IconLayers,
- } from '@douyinfe/semi-icons';
- import {
- IllustrationNoResult,
- IllustrationNoResultDark,
- } from '@douyinfe/semi-illustrations';
- import { API, showError, showSuccess, stringToColor } from '../../../../helpers';
- import { useTranslation } from 'react-i18next';
- import { useIsMobile } from '../../../../hooks/common/useIsMobile';
- import CardTable from '../../../common/ui/CardTable';
- import EditPrefillGroupModal from './EditPrefillGroupModal';
- import { renderLimitedItems, renderDescription } from '../../../common/ui/RenderUtils';
- const { Text, Title } = Typography;
- const PrefillGroupManagement = ({ visible, onClose }) => {
- const { t } = useTranslation();
- const isMobile = useIsMobile();
- const [loading, setLoading] = useState(false);
- const [groups, setGroups] = useState([]);
- const [showEdit, setShowEdit] = useState(false);
- const [editingGroup, setEditingGroup] = useState({ id: undefined });
- const typeOptions = [
- { label: t('模型组'), value: 'model' },
- { label: t('标签组'), value: 'tag' },
- { label: t('端点组'), value: 'endpoint' },
- ];
- // 加载组列表
- const loadGroups = async () => {
- setLoading(true);
- try {
- const res = await API.get('/api/prefill_group');
- if (res.data.success) {
- setGroups(res.data.data || []);
- } else {
- showError(res.data.message || t('获取组列表失败'));
- }
- } catch (error) {
- showError(t('获取组列表失败'));
- }
- setLoading(false);
- };
- // 删除组
- const deleteGroup = async (id) => {
- try {
- const res = await API.delete(`/api/prefill_group/${id}`);
- if (res.data.success) {
- showSuccess(t('删除成功'));
- loadGroups();
- } else {
- showError(res.data.message || t('删除失败'));
- }
- } catch (error) {
- showError(t('删除失败'));
- }
- };
- // 编辑组
- const handleEdit = (group = {}) => {
- setEditingGroup(group);
- setShowEdit(true);
- };
- // 关闭编辑
- const closeEdit = () => {
- setShowEdit(false);
- setTimeout(() => {
- setEditingGroup({ id: undefined });
- }, 300);
- };
- // 编辑成功回调
- const handleEditSuccess = () => {
- closeEdit();
- loadGroups();
- };
- // 表格列定义
- const columns = [
- {
- title: t('组名'),
- dataIndex: 'name',
- key: 'name',
- render: (text, record) => (
- <Space>
- <Text strong>{text}</Text>
- <Tag color="white" shape="circle" size="small">
- {typeOptions.find(opt => opt.value === record.type)?.label || record.type}
- </Tag>
- </Space>
- ),
- },
- {
- title: t('描述'),
- dataIndex: 'description',
- key: 'description',
- render: (text) => renderDescription(text, 150),
- },
- {
- title: t('项目内容'),
- dataIndex: 'items',
- key: 'items',
- render: (items) => {
- try {
- const itemsArray = typeof items === 'string' ? JSON.parse(items) : items;
- if (!Array.isArray(itemsArray) || itemsArray.length === 0) {
- return <Text type="tertiary">{t('暂无项目')}</Text>;
- }
- return renderLimitedItems({
- items: itemsArray,
- renderItem: (item, idx) => (
- <Tag key={idx} size="small" shape='circle' color={stringToColor(item)}>
- {item}
- </Tag>
- ),
- maxDisplay: 3,
- });
- } catch {
- return <Text type="tertiary">{t('数据格式错误')}</Text>;
- }
- },
- },
- {
- title: '',
- key: 'action',
- fixed: 'right',
- width: 140,
- render: (_, record) => (
- <Space>
- <Button
- size="small"
- onClick={() => handleEdit(record)}
- >
- {t('编辑')}
- </Button>
- <Popconfirm
- title={t('确定删除此组?')}
- onConfirm={() => deleteGroup(record.id)}
- >
- <Button
- size="small"
- type="danger"
- >
- {t('删除')}
- </Button>
- </Popconfirm>
- </Space>
- ),
- },
- ];
- useEffect(() => {
- if (visible) {
- loadGroups();
- }
- }, [visible]);
- return (
- <>
- <SideSheet
- placement="left"
- title={
- <Space>
- <Tag color='blue' shape='circle'>
- {t('管理')}
- </Tag>
- <Title heading={4} className='m-0'>
- {t('预填组管理')}
- </Title>
- </Space>
- }
- visible={visible}
- onCancel={onClose}
- width={isMobile ? '100%' : 800}
- bodyStyle={{ padding: '0' }}
- closeIcon={null}
- >
- <Spin spinning={loading}>
- <div className='p-2'>
- <Card className='!rounded-2xl shadow-sm border-0'>
- <div className='flex items-center mb-2'>
- <Avatar size='small' color='blue' className='mr-2 shadow-md'>
- <IconLayers size={16} />
- </Avatar>
- <div>
- <Text className='text-lg font-medium'>{t('组列表')}</Text>
- <div className='text-xs text-gray-600'>{t('管理模型、标签、端点等预填组')}</div>
- </div>
- </div>
- <div className="flex justify-end mb-4">
- <Button
- type="primary"
- theme='solid'
- size="small"
- icon={<IconPlus />}
- onClick={() => handleEdit()}
- >
- {t('新建组')}
- </Button>
- </div>
- {groups.length > 0 ? (
- <CardTable
- columns={columns}
- dataSource={groups}
- rowKey="id"
- hidePagination={true}
- size="small"
- scroll={{ x: 'max-content' }}
- />
- ) : (
- <Empty
- image={<IllustrationNoResult style={{ width: 150, height: 150 }} />}
- darkModeImage={<IllustrationNoResultDark style={{ width: 150, height: 150 }} />}
- description={t('暂无预填组')}
- style={{ padding: 30 }}
- />
- )}
- </Card>
- </div>
- </Spin>
- </SideSheet>
- {/* 编辑组件 */}
- <EditPrefillGroupModal
- visible={showEdit}
- onClose={closeEdit}
- editingGroup={editingGroup}
- onSuccess={handleEditSuccess}
- />
- </>
- );
- };
- export default PrefillGroupManagement;
|