初始化导入 new-api 源码
This commit is contained in:
@@ -0,0 +1,309 @@
|
||||
/*
|
||||
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' },
|
||||
{ label: t('订阅分组'), value: 'subscription_group' },
|
||||
];
|
||||
|
||||
// 加载组列表
|
||||
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, record) => {
|
||||
try {
|
||||
if (record.type === 'endpoint') {
|
||||
const obj =
|
||||
typeof items === 'string'
|
||||
? JSON.parse(items || '{}')
|
||||
: items || {};
|
||||
const keys = Object.keys(obj);
|
||||
if (keys.length === 0)
|
||||
return <Text type='tertiary'>{t('暂无项目')}</Text>;
|
||||
return renderLimitedItems({
|
||||
items: keys,
|
||||
renderItem: (key, idx) => (
|
||||
<Tag
|
||||
key={idx}
|
||||
size='small'
|
||||
shape='circle'
|
||||
color={stringToColor(key)}
|
||||
>
|
||||
{key}
|
||||
</Tag>
|
||||
),
|
||||
maxDisplay: 3,
|
||||
});
|
||||
}
|
||||
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;
|
||||
Reference in New Issue
Block a user