feat: Add prefill group management system for models

- Add new PrefillGroup model with CRUD operations
  * Support for model, tag, and endpoint group types
  * JSON storage for group items with GORM datatypes
  * Automatic database migration support

- Implement backend API endpoints
  * GET /api/prefill_group - List groups by type with admin auth
  * POST /api/prefill_group - Create new groups
  * PUT /api/prefill_group - Update existing groups
  * DELETE /api/prefill_group/:id - Delete groups

- Add comprehensive frontend management interface
  * PrefillGroupManagement component for group listing
  * EditPrefillGroupModal for group creation/editing
  * Integration with EditModelModal for auto-filling
  * Responsive design with CardTable and SideSheet

- Enhance model editing workflow
  * Tag group selection with auto-fill functionality
  * Endpoint group selection with auto-fill functionality
  * Seamless integration with existing model forms

- Create reusable UI components
  * Extract common rendering utilities to models/ui/
  * Shared renderLimitedItems and renderDescription functions
  * Consistent styling across all model-related components

- Improve user experience
  * Empty state illustrations matching existing patterns
  * Fixed column positioning for operation buttons
  * Item content display with +x indicators for overflow
  * Tooltip support for long descriptions
This commit is contained in:
t0ng7u
2025-08-04 02:54:37 +08:00
parent b64c8ea56b
commit 9f6027325c
13 changed files with 803 additions and 45 deletions

View File

@@ -23,14 +23,14 @@ import {
Space,
Tag,
Typography,
Modal,
Popover
Modal
} from '@douyinfe/semi-ui';
import {
timestamp2string,
getLobeHubIcon,
stringToColor
} from '../../../helpers';
import { renderLimitedItems, renderDescription } from './ui/RenderUtils.jsx';
const { Text } = Typography;
@@ -39,34 +39,6 @@ function renderTimestamp(timestamp) {
return <>{timestamp2string(timestamp)}</>;
}
// Generic renderer for list-style tags with limit and popover
function renderLimitedItems({ items, renderItem, maxDisplay = 3 }) {
if (!items || items.length === 0) return '-';
const displayItems = items.slice(0, maxDisplay);
const remainingItems = items.slice(maxDisplay);
return (
<Space spacing={1} wrap>
{displayItems.map((item, idx) => renderItem(item, idx))}
{remainingItems.length > 0 && (
<Popover
content={
<div className='p-2'>
<Space spacing={1} wrap>
{remainingItems.map((item, idx) => renderItem(item, idx))}
</Space>
</div>
}
position='top'
>
<Tag size='small' shape='circle' color='grey'>
+{remainingItems.length}
</Tag>
</Popover>
)}
</Space>
);
}
// Render vendor column with icon
const renderVendorTag = (vendorId, vendorMap, t) => {
if (!vendorId || !vendorMap[vendorId]) return '-';
@@ -82,15 +54,6 @@ const renderVendorTag = (vendorId, vendorMap, t) => {
);
};
// Render description with ellipsis
const renderDescription = (text) => {
return (
<Text ellipsis={{ showTooltip: true }} style={{ maxWidth: 200 }}>
{text || '-'}
</Text>
);
};
// Render groups (enable_groups)
const renderGroups = (groups) => {
if (!groups || groups.length === 0) return '-';
@@ -223,7 +186,7 @@ export const getModelsColumns = ({
{
title: t('描述'),
dataIndex: 'description',
render: renderDescription,
render: (text) => renderDescription(text, 200),
},
{
title: t('供应商'),