🎨 chore(web): apply ESLint and Prettier auto-fixes (baseline)
- Ran: bun run eslint:fix && bun run lint:fix - Inserted AGPL license header via eslint-plugin-header - Enforced no-multiple-empty-lines and other lint rules - Formatted code using Prettier v3 (@so1ve/prettier-config) - No functional changes; formatting-only baseline across JS/JSX files
This commit is contained in:
@@ -61,7 +61,7 @@ const ModelsActions = ({
|
||||
|
||||
// Handle add selected models to prefill group
|
||||
const handleCopyNames = async () => {
|
||||
const text = selectedKeys.map(m => m.model_name).join(',');
|
||||
const text = selectedKeys.map((m) => m.model_name).join(',');
|
||||
if (!text) return;
|
||||
const ok = await copy(text);
|
||||
if (ok) {
|
||||
@@ -80,34 +80,34 @@ const ModelsActions = ({
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-wrap gap-2 w-full md:w-auto order-2 md:order-1">
|
||||
<div className='flex flex-wrap gap-2 w-full md:w-auto order-2 md:order-1'>
|
||||
<Button
|
||||
type="primary"
|
||||
className="flex-1 md:flex-initial"
|
||||
type='primary'
|
||||
className='flex-1 md:flex-initial'
|
||||
onClick={() => {
|
||||
setEditingModel({
|
||||
id: undefined,
|
||||
});
|
||||
setShowEdit(true);
|
||||
}}
|
||||
size="small"
|
||||
size='small'
|
||||
>
|
||||
{t('添加模型')}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
type="secondary"
|
||||
className="flex-1 md:flex-initial"
|
||||
size="small"
|
||||
type='secondary'
|
||||
className='flex-1 md:flex-initial'
|
||||
size='small'
|
||||
onClick={() => setShowMissingModal(true)}
|
||||
>
|
||||
{t('未配置模型')}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
type="secondary"
|
||||
className="flex-1 md:flex-initial"
|
||||
size="small"
|
||||
type='secondary'
|
||||
className='flex-1 md:flex-initial'
|
||||
size='small'
|
||||
onClick={() => setShowGroupManagement(true)}
|
||||
>
|
||||
{t('预填组管理')}
|
||||
@@ -134,10 +134,12 @@ const ModelsActions = ({
|
||||
visible={showDeleteModal}
|
||||
onCancel={() => setShowDeleteModal(false)}
|
||||
onOk={handleConfirmDelete}
|
||||
type="warning"
|
||||
type='warning'
|
||||
>
|
||||
<div>
|
||||
{t('确定要删除所选的 {{count}} 个模型吗?', { count: selectedKeys.length })}
|
||||
{t('确定要删除所选的 {{count}} 个模型吗?', {
|
||||
count: selectedKeys.length,
|
||||
})}
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
@@ -167,4 +169,4 @@ const ModelsActions = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default ModelsActions;
|
||||
export default ModelsActions;
|
||||
|
||||
@@ -18,13 +18,23 @@ For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { Button, Space, Tag, Typography, Modal, Tooltip } from '@douyinfe/semi-ui';
|
||||
import {
|
||||
Button,
|
||||
Space,
|
||||
Tag,
|
||||
Typography,
|
||||
Modal,
|
||||
Tooltip,
|
||||
} from '@douyinfe/semi-ui';
|
||||
import {
|
||||
timestamp2string,
|
||||
getLobeHubIcon,
|
||||
stringToColor
|
||||
stringToColor,
|
||||
} from '../../../helpers';
|
||||
import { renderLimitedItems, renderDescription } from '../../common/ui/RenderUtils';
|
||||
import {
|
||||
renderLimitedItems,
|
||||
renderDescription,
|
||||
} from '../../common/ui/RenderUtils';
|
||||
|
||||
const { Text } = Typography;
|
||||
|
||||
@@ -38,7 +48,7 @@ const renderModelIconCol = (record, vendorMap) => {
|
||||
const iconKey = record?.icon || vendorMap[record?.vendor_id]?.icon;
|
||||
if (!iconKey) return '-';
|
||||
return (
|
||||
<div className="flex items-center justify-center">
|
||||
<div className='flex items-center justify-center'>
|
||||
{getLobeHubIcon(iconKey, 20)}
|
||||
</div>
|
||||
);
|
||||
@@ -65,7 +75,7 @@ const renderGroups = (groups) => {
|
||||
return renderLimitedItems({
|
||||
items: groups,
|
||||
renderItem: (g, idx) => (
|
||||
<Tag key={idx} size="small" shape='circle' color={stringToColor(g)}>
|
||||
<Tag key={idx} size='small' shape='circle' color={stringToColor(g)}>
|
||||
{g}
|
||||
</Tag>
|
||||
),
|
||||
@@ -79,7 +89,7 @@ const renderTags = (text) => {
|
||||
return renderLimitedItems({
|
||||
items: tagsArr,
|
||||
renderItem: (tag, idx) => (
|
||||
<Tag key={idx} size="small" shape='circle' color={stringToColor(tag)}>
|
||||
<Tag key={idx} size='small' shape='circle' color={stringToColor(tag)}>
|
||||
{tag}
|
||||
</Tag>
|
||||
),
|
||||
@@ -96,7 +106,7 @@ const renderEndpoints = (value) => {
|
||||
return renderLimitedItems({
|
||||
items: keys,
|
||||
renderItem: (key, idx) => (
|
||||
<Tag key={idx} size="small" shape='circle' color={stringToColor(key)}>
|
||||
<Tag key={idx} size='small' shape='circle' color={stringToColor(key)}>
|
||||
{key}
|
||||
</Tag>
|
||||
),
|
||||
@@ -108,7 +118,7 @@ const renderEndpoints = (value) => {
|
||||
return renderLimitedItems({
|
||||
items: parsed,
|
||||
renderItem: (ep, idx) => (
|
||||
<Tag key={idx} color="white" size="small" shape='circle'>
|
||||
<Tag key={idx} color='white' size='small' shape='circle'>
|
||||
{ep}
|
||||
</Tag>
|
||||
),
|
||||
@@ -157,7 +167,7 @@ const renderBoundChannels = (channels) => {
|
||||
return renderLimitedItems({
|
||||
items: channels,
|
||||
renderItem: (c, idx) => (
|
||||
<Tag key={idx} color="white" size="small" shape='circle'>
|
||||
<Tag key={idx} color='white' size='small' shape='circle'>
|
||||
{c.name}({c.type})
|
||||
</Tag>
|
||||
),
|
||||
@@ -165,20 +175,28 @@ const renderBoundChannels = (channels) => {
|
||||
};
|
||||
|
||||
// Render operations column
|
||||
const renderOperations = (text, record, setEditingModel, setShowEdit, manageModel, refresh, t) => {
|
||||
const renderOperations = (
|
||||
text,
|
||||
record,
|
||||
setEditingModel,
|
||||
setShowEdit,
|
||||
manageModel,
|
||||
refresh,
|
||||
t,
|
||||
) => {
|
||||
return (
|
||||
<Space wrap>
|
||||
{record.status === 1 ? (
|
||||
<Button
|
||||
type='danger'
|
||||
size="small"
|
||||
size='small'
|
||||
onClick={() => manageModel(record.id, 'disable', record)}
|
||||
>
|
||||
{t('禁用')}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
size="small"
|
||||
size='small'
|
||||
onClick={() => manageModel(record.id, 'enable', record)}
|
||||
>
|
||||
{t('启用')}
|
||||
@@ -187,7 +205,7 @@ const renderOperations = (text, record, setEditingModel, setShowEdit, manageMode
|
||||
|
||||
<Button
|
||||
type='tertiary'
|
||||
size="small"
|
||||
size='small'
|
||||
onClick={() => {
|
||||
setEditingModel(record);
|
||||
setShowEdit(true);
|
||||
@@ -198,7 +216,7 @@ const renderOperations = (text, record, setEditingModel, setShowEdit, manageMode
|
||||
|
||||
<Button
|
||||
type='danger'
|
||||
size="small"
|
||||
size='small'
|
||||
onClick={() => {
|
||||
Modal.confirm({
|
||||
title: t('确定是否要删除此模型?'),
|
||||
@@ -235,12 +253,16 @@ const renderNameRule = (rule, record, t) => {
|
||||
}
|
||||
|
||||
const tagElement = (
|
||||
<Tag color={cfg.color} size="small" shape='circle'>
|
||||
<Tag color={cfg.color} size='small' shape='circle'>
|
||||
{label}
|
||||
</Tag>
|
||||
);
|
||||
|
||||
if (rule === 0 || !record.matched_models || record.matched_models.length === 0) {
|
||||
if (
|
||||
rule === 0 ||
|
||||
!record.matched_models ||
|
||||
record.matched_models.length === 0
|
||||
) {
|
||||
return tagElement;
|
||||
}
|
||||
|
||||
@@ -334,15 +356,16 @@ export const getModelsColumns = ({
|
||||
title: '',
|
||||
dataIndex: 'operate',
|
||||
fixed: 'right',
|
||||
render: (text, record, index) => renderOperations(
|
||||
text,
|
||||
record,
|
||||
setEditingModel,
|
||||
setShowEdit,
|
||||
manageModel,
|
||||
refresh,
|
||||
t
|
||||
),
|
||||
render: (text, record, index) =>
|
||||
renderOperations(
|
||||
text,
|
||||
record,
|
||||
setEditingModel,
|
||||
setShowEdit,
|
||||
manageModel,
|
||||
refresh,
|
||||
t,
|
||||
),
|
||||
},
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
@@ -26,9 +26,9 @@ const { Text } = Typography;
|
||||
|
||||
const ModelsDescription = ({ compactMode, setCompactMode, t }) => {
|
||||
return (
|
||||
<div className="flex flex-col md:flex-row justify-between items-start md:items-center gap-2 w-full">
|
||||
<div className="flex items-center text-green-500">
|
||||
<Layers size={16} className="mr-2" />
|
||||
<div className='flex flex-col md:flex-row justify-between items-start md:items-center gap-2 w-full'>
|
||||
<div className='flex items-center text-green-500'>
|
||||
<Layers size={16} className='mr-2' />
|
||||
<Text>{t('模型管理')}</Text>
|
||||
</div>
|
||||
|
||||
@@ -41,4 +41,4 @@ const ModelsDescription = ({ compactMode, setCompactMode, t }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default ModelsDescription;
|
||||
export default ModelsDescription;
|
||||
|
||||
@@ -49,42 +49,42 @@ const ModelsFilters = ({
|
||||
}}
|
||||
onSubmit={searchModels}
|
||||
allowEmpty={true}
|
||||
autoComplete="off"
|
||||
layout="horizontal"
|
||||
trigger="change"
|
||||
autoComplete='off'
|
||||
layout='horizontal'
|
||||
trigger='change'
|
||||
stopValidateWithError={false}
|
||||
className="w-full md:w-auto order-1 md:order-2"
|
||||
className='w-full md:w-auto order-1 md:order-2'
|
||||
>
|
||||
<div className="flex flex-col md:flex-row items-center gap-2 w-full md:w-auto">
|
||||
<div className="relative w-full md:w-56">
|
||||
<div className='flex flex-col md:flex-row items-center gap-2 w-full md:w-auto'>
|
||||
<div className='relative w-full md:w-56'>
|
||||
<Form.Input
|
||||
field="searchKeyword"
|
||||
field='searchKeyword'
|
||||
prefix={<IconSearch />}
|
||||
placeholder={t('搜索模型名称')}
|
||||
showClear
|
||||
pure
|
||||
size="small"
|
||||
size='small'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="relative w-full md:w-56">
|
||||
<div className='relative w-full md:w-56'>
|
||||
<Form.Input
|
||||
field="searchVendor"
|
||||
field='searchVendor'
|
||||
prefix={<IconSearch />}
|
||||
placeholder={t('搜索供应商')}
|
||||
showClear
|
||||
pure
|
||||
size="small"
|
||||
size='small'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-2 w-full md:w-auto">
|
||||
<div className='flex gap-2 w-full md:w-auto'>
|
||||
<Button
|
||||
type="tertiary"
|
||||
htmlType="submit"
|
||||
type='tertiary'
|
||||
htmlType='submit'
|
||||
loading={loading || searching}
|
||||
className="flex-1 md:flex-initial md:w-auto"
|
||||
size="small"
|
||||
className='flex-1 md:flex-initial md:w-auto'
|
||||
size='small'
|
||||
>
|
||||
{t('查询')}
|
||||
</Button>
|
||||
@@ -92,8 +92,8 @@ const ModelsFilters = ({
|
||||
<Button
|
||||
type='tertiary'
|
||||
onClick={handleReset}
|
||||
className="flex-1 md:flex-initial md:w-auto"
|
||||
size="small"
|
||||
className='flex-1 md:flex-initial md:w-auto'
|
||||
size='small'
|
||||
>
|
||||
{t('重置')}
|
||||
</Button>
|
||||
@@ -103,4 +103,4 @@ const ModelsFilters = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default ModelsFilters;
|
||||
export default ModelsFilters;
|
||||
|
||||
@@ -60,13 +60,15 @@ const ModelsTable = (modelsData) => {
|
||||
|
||||
// Handle compact mode by removing fixed positioning
|
||||
const tableColumns = useMemo(() => {
|
||||
return compactMode ? columns.map(col => {
|
||||
if (col.dataIndex === 'operate') {
|
||||
const { fixed, ...rest } = col;
|
||||
return rest;
|
||||
}
|
||||
return col;
|
||||
}) : columns;
|
||||
return compactMode
|
||||
? columns.map((col) => {
|
||||
if (col.dataIndex === 'operate') {
|
||||
const { fixed, ...rest } = col;
|
||||
return rest;
|
||||
}
|
||||
return col;
|
||||
})
|
||||
: columns;
|
||||
}, [compactMode, columns]);
|
||||
|
||||
return (
|
||||
@@ -90,15 +92,17 @@ const ModelsTable = (modelsData) => {
|
||||
empty={
|
||||
<Empty
|
||||
image={<IllustrationNoResult style={{ width: 150, height: 150 }} />}
|
||||
darkModeImage={<IllustrationNoResultDark style={{ width: 150, height: 150 }} />}
|
||||
darkModeImage={
|
||||
<IllustrationNoResultDark style={{ width: 150, height: 150 }} />
|
||||
}
|
||||
description={t('搜索无结果')}
|
||||
style={{ padding: 30 }}
|
||||
/>
|
||||
}
|
||||
className="rounded-xl overflow-hidden"
|
||||
size="middle"
|
||||
className='rounded-xl overflow-hidden'
|
||||
size='middle'
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default ModelsTable;
|
||||
export default ModelsTable;
|
||||
|
||||
@@ -36,7 +36,7 @@ const ModelsTabs = ({
|
||||
setShowEditVendor,
|
||||
setEditingVendor,
|
||||
loadVendors,
|
||||
t
|
||||
t,
|
||||
}) => {
|
||||
const handleTabChange = (key) => {
|
||||
setActiveVendorKey(key);
|
||||
@@ -75,14 +75,14 @@ const ModelsTabs = ({
|
||||
return (
|
||||
<Tabs
|
||||
activeKey={activeVendorKey}
|
||||
type="card"
|
||||
type='card'
|
||||
collapsible
|
||||
onChange={handleTabChange}
|
||||
className="mb-2"
|
||||
className='mb-2'
|
||||
tabBarExtraContent={
|
||||
<Button
|
||||
type="primary"
|
||||
size="small"
|
||||
type='primary'
|
||||
size='small'
|
||||
onClick={() => setShowAddVendor(true)}
|
||||
>
|
||||
{t('新增供应商')}
|
||||
@@ -90,11 +90,14 @@ const ModelsTabs = ({
|
||||
}
|
||||
>
|
||||
<TabPane
|
||||
itemKey="all"
|
||||
itemKey='all'
|
||||
tab={
|
||||
<span className="flex items-center gap-2">
|
||||
<span className='flex items-center gap-2'>
|
||||
{t('全部')}
|
||||
<Tag color={activeVendorKey === 'all' ? 'red' : 'grey'} shape='circle'>
|
||||
<Tag
|
||||
color={activeVendorKey === 'all' ? 'red' : 'grey'}
|
||||
shape='circle'
|
||||
>
|
||||
{vendorCounts['all'] || 0}
|
||||
</Tag>
|
||||
</span>
|
||||
@@ -109,15 +112,18 @@ const ModelsTabs = ({
|
||||
key={key}
|
||||
itemKey={key}
|
||||
tab={
|
||||
<span className="flex items-center gap-2">
|
||||
<span className='flex items-center gap-2'>
|
||||
{getLobeHubIcon(vendor.icon || 'Layers', 14)}
|
||||
{vendor.name}
|
||||
<Tag color={activeVendorKey === key ? 'red' : 'grey'} shape='circle'>
|
||||
<Tag
|
||||
color={activeVendorKey === key ? 'red' : 'grey'}
|
||||
shape='circle'
|
||||
>
|
||||
{count}
|
||||
</Tag>
|
||||
<Dropdown
|
||||
trigger="click"
|
||||
position="bottomRight"
|
||||
trigger='click'
|
||||
position='bottomRight'
|
||||
render={
|
||||
<Dropdown.Menu>
|
||||
<Dropdown.Item
|
||||
@@ -127,13 +133,16 @@ const ModelsTabs = ({
|
||||
{t('编辑')}
|
||||
</Dropdown.Item>
|
||||
<Dropdown.Item
|
||||
type="danger"
|
||||
type='danger'
|
||||
icon={<IconDelete />}
|
||||
onClick={(e) => {
|
||||
e.stopPropagation();
|
||||
Modal.confirm({
|
||||
title: t('确认删除'),
|
||||
content: t('确定要删除供应商 "{{name}}" 吗?此操作不可撤销。', { name: vendor.name }),
|
||||
content: t(
|
||||
'确定要删除供应商 "{{name}}" 吗?此操作不可撤销。',
|
||||
{ name: vendor.name },
|
||||
),
|
||||
onOk: () => handleDeleteVendor(vendor, e),
|
||||
okText: t('删除'),
|
||||
cancelText: t('取消'),
|
||||
@@ -149,9 +158,9 @@ const ModelsTabs = ({
|
||||
onClickOutSide={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Button
|
||||
size="small"
|
||||
type="tertiary"
|
||||
theme="outline"
|
||||
size='small'
|
||||
type='tertiary'
|
||||
theme='outline'
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{t('操作')}
|
||||
@@ -166,4 +175,4 @@ const ModelsTabs = ({
|
||||
);
|
||||
};
|
||||
|
||||
export default ModelsTabs;
|
||||
export default ModelsTabs;
|
||||
|
||||
@@ -28,7 +28,14 @@ const NOTICE_ID = 'models-batch-actions';
|
||||
* 1. 当 selectedKeys.length > 0 时,使用固定 id 创建/更新通知
|
||||
* 2. 当 selectedKeys 清空时关闭通知
|
||||
*/
|
||||
const SelectionNotification = ({ selectedKeys = [], t, onDelete, onAddPrefill, onClear, onCopy }) => {
|
||||
const SelectionNotification = ({
|
||||
selectedKeys = [],
|
||||
t,
|
||||
onDelete,
|
||||
onAddPrefill,
|
||||
onClear,
|
||||
onCopy,
|
||||
}) => {
|
||||
// 根据选中数量决定显示/隐藏或更新通知
|
||||
useEffect(() => {
|
||||
const selectedCount = selectedKeys.length;
|
||||
@@ -37,42 +44,29 @@ const SelectionNotification = ({ selectedKeys = [], t, onDelete, onAddPrefill, o
|
||||
const titleNode = (
|
||||
<Space wrap>
|
||||
<span>{t('批量操作')}</span>
|
||||
<Typography.Text type="tertiary" size="small">{t('已选择 {{count}} 个模型', { count: selectedCount })}</Typography.Text>
|
||||
<Typography.Text type='tertiary' size='small'>
|
||||
{t('已选择 {{count}} 个模型', { count: selectedCount })}
|
||||
</Typography.Text>
|
||||
</Space>
|
||||
);
|
||||
|
||||
const content = (
|
||||
<Space wrap>
|
||||
<Button
|
||||
size="small"
|
||||
type="tertiary"
|
||||
theme="solid"
|
||||
onClick={onClear}
|
||||
>
|
||||
<Button size='small' type='tertiary' theme='solid' onClick={onClear}>
|
||||
{t('取消全选')}
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
type="primary"
|
||||
theme="solid"
|
||||
size='small'
|
||||
type='primary'
|
||||
theme='solid'
|
||||
onClick={onAddPrefill}
|
||||
>
|
||||
{t('加入预填组')}
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
type="secondary"
|
||||
theme="solid"
|
||||
onClick={onCopy}
|
||||
>
|
||||
<Button size='small' type='secondary' theme='solid' onClick={onCopy}>
|
||||
{t('复制名称')}
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
type="danger"
|
||||
theme="solid"
|
||||
onClick={onDelete}
|
||||
>
|
||||
<Button size='small' type='danger' theme='solid' onClick={onDelete}>
|
||||
{t('删除所选')}
|
||||
</Button>
|
||||
</Space>
|
||||
|
||||
@@ -95,10 +95,10 @@ const ModelsPage = () => {
|
||||
/>
|
||||
|
||||
<CardPro
|
||||
type="type3"
|
||||
type='type3'
|
||||
tabsArea={<ModelsTabs {...modelsData} />}
|
||||
actionsArea={
|
||||
<div className="flex flex-col md:flex-row justify-between items-center gap-2 w-full">
|
||||
<div className='flex flex-col md:flex-row justify-between items-center gap-2 w-full'>
|
||||
<ModelsActions
|
||||
selectedKeys={selectedKeys}
|
||||
setSelectedKeys={setSelectedKeys}
|
||||
@@ -110,7 +110,7 @@ const ModelsPage = () => {
|
||||
t={t}
|
||||
/>
|
||||
|
||||
<div className="w-full md:w-full lg:w-auto order-1 md:order-2">
|
||||
<div className='w-full md:w-full lg:w-auto order-1 md:order-2'>
|
||||
<ModelsFilters
|
||||
formInitValues={formInitValues}
|
||||
setFormApi={setFormApi}
|
||||
|
||||
@@ -290,7 +290,9 @@ const EditModelModal = (props) => {
|
||||
</Avatar>
|
||||
<div>
|
||||
<Text className='text-lg font-medium'>{t('基本信息')}</Text>
|
||||
<div className='text-xs text-gray-600'>{t('设置模型的基本信息')}</div>
|
||||
<div className='text-xs text-gray-600'>
|
||||
{t('设置模型的基本信息')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Row gutter={12}>
|
||||
@@ -309,9 +311,16 @@ const EditModelModal = (props) => {
|
||||
field='name_rule'
|
||||
label={t('名称匹配类型')}
|
||||
placeholder={t('请选择名称匹配类型')}
|
||||
optionList={nameRuleOptions.map(o => ({ label: t(o.label), value: o.value }))}
|
||||
rules={[{ required: true, message: t('请选择名称匹配类型') }]}
|
||||
extraText={t('根据模型名称和匹配规则查找模型元数据,优先级:精确 > 前缀 > 后缀 > 包含')}
|
||||
optionList={nameRuleOptions.map((o) => ({
|
||||
label: t(o.label),
|
||||
value: o.value,
|
||||
}))}
|
||||
rules={[
|
||||
{ required: true, message: t('请选择名称匹配类型') },
|
||||
]}
|
||||
extraText={t(
|
||||
'根据模型名称和匹配规则查找模型元数据,优先级:精确 > 前缀 > 后缀 > 包含',
|
||||
)}
|
||||
style={{ width: '100%' }}
|
||||
/>
|
||||
</Col>
|
||||
@@ -323,9 +332,14 @@ const EditModelModal = (props) => {
|
||||
placeholder={t('请输入图标名称')}
|
||||
extraText={
|
||||
<span>
|
||||
{t('图标使用@lobehub/icons库,如:OpenAI、Claude.Color,支持链式参数:OpenAI.Avatar.type={\'platform\'}、OpenRouter.Avatar.shape={\'square\'},查询所有可用图标请 ')}
|
||||
{t(
|
||||
"图标使用@lobehub/icons库,如:OpenAI、Claude.Color,支持链式参数:OpenAI.Avatar.type={'platform'}、OpenRouter.Avatar.shape={'square'},查询所有可用图标请 ",
|
||||
)}
|
||||
<Typography.Text
|
||||
link={{ href: 'https://icons.lobehub.com/components/lobe-hub', target: '_blank' }}
|
||||
link={{
|
||||
href: 'https://icons.lobehub.com/components/lobe-hub',
|
||||
target: '_blank',
|
||||
}}
|
||||
icon={<IconLink />}
|
||||
underline
|
||||
>
|
||||
@@ -357,7 +371,16 @@ const EditModelModal = (props) => {
|
||||
if (!formApiRef.current) return;
|
||||
const normalize = (tags) => {
|
||||
if (!Array.isArray(tags)) return [];
|
||||
return [...new Set(tags.flatMap(tag => tag.split(',').map(t => t.trim()).filter(Boolean)))];
|
||||
return [
|
||||
...new Set(
|
||||
tags.flatMap((tag) =>
|
||||
tag
|
||||
.split(',')
|
||||
.map((t) => t.trim())
|
||||
.filter(Boolean),
|
||||
),
|
||||
),
|
||||
];
|
||||
};
|
||||
const normalized = normalize(newTags);
|
||||
formApiRef.current.setValue('tags', normalized);
|
||||
@@ -366,17 +389,24 @@ const EditModelModal = (props) => {
|
||||
{...(tagGroups.length > 0 && {
|
||||
extraText: (
|
||||
<Space wrap>
|
||||
{tagGroups.map(group => (
|
||||
{tagGroups.map((group) => (
|
||||
<Button
|
||||
key={group.id}
|
||||
size='small'
|
||||
type='primary'
|
||||
onClick={() => {
|
||||
if (formApiRef.current) {
|
||||
const currentTags = formApiRef.current.getValue('tags') || [];
|
||||
const newTags = [...currentTags, ...(group.items || [])];
|
||||
const currentTags =
|
||||
formApiRef.current.getValue('tags') || [];
|
||||
const newTags = [
|
||||
...currentTags,
|
||||
...(group.items || []),
|
||||
];
|
||||
const uniqueTags = [...new Set(newTags)];
|
||||
formApiRef.current.setValue('tags', uniqueTags);
|
||||
formApiRef.current.setValue(
|
||||
'tags',
|
||||
uniqueTags,
|
||||
);
|
||||
}
|
||||
}}
|
||||
>
|
||||
@@ -384,7 +414,7 @@ const EditModelModal = (props) => {
|
||||
</Button>
|
||||
))}
|
||||
</Space>
|
||||
)
|
||||
),
|
||||
})}
|
||||
/>
|
||||
</Col>
|
||||
@@ -393,13 +423,19 @@ const EditModelModal = (props) => {
|
||||
field='vendor_id'
|
||||
label={t('供应商')}
|
||||
placeholder={t('选择模型供应商')}
|
||||
optionList={vendors.map(v => ({ label: v.name, value: v.id }))}
|
||||
optionList={vendors.map((v) => ({
|
||||
label: v.name,
|
||||
value: v.id,
|
||||
}))}
|
||||
filter
|
||||
showClear
|
||||
onChange={(value) => {
|
||||
const vendorInfo = vendors.find(v => v.id === value);
|
||||
const vendorInfo = vendors.find((v) => v.id === value);
|
||||
if (vendorInfo && formApiRef.current) {
|
||||
formApiRef.current.setValue('vendor', vendorInfo.name);
|
||||
formApiRef.current.setValue(
|
||||
'vendor',
|
||||
vendorInfo.name,
|
||||
);
|
||||
}
|
||||
}}
|
||||
style={{ width: '100%' }}
|
||||
@@ -409,49 +445,71 @@ const EditModelModal = (props) => {
|
||||
<JSONEditor
|
||||
field='endpoints'
|
||||
label={t('端点映射')}
|
||||
placeholder={'{\n "openai": {"path": "/v1/chat/completions", "method": "POST"}\n}'}
|
||||
placeholder={
|
||||
'{\n "openai": {"path": "/v1/chat/completions", "method": "POST"}\n}'
|
||||
}
|
||||
value={values.endpoints}
|
||||
onChange={(val) => formApiRef.current?.setValue('endpoints', val)}
|
||||
onChange={(val) =>
|
||||
formApiRef.current?.setValue('endpoints', val)
|
||||
}
|
||||
formApi={formApiRef.current}
|
||||
editorType='object'
|
||||
template={ENDPOINT_TEMPLATE}
|
||||
templateLabel={t('填入模板')}
|
||||
extraText={t('留空则使用默认端点;支持 {path, method}')}
|
||||
extraFooter={endpointGroups.length > 0 && (
|
||||
<Space wrap>
|
||||
{endpointGroups.map(group => (
|
||||
<Button
|
||||
key={group.id}
|
||||
size='small'
|
||||
type='primary'
|
||||
onClick={() => {
|
||||
try {
|
||||
const current = formApiRef.current?.getValue('endpoints') || '';
|
||||
let base = {};
|
||||
if (current && current.trim()) base = JSON.parse(current);
|
||||
const groupObj = typeof group.items === 'string' ? JSON.parse(group.items || '{}') : (group.items || {});
|
||||
const merged = { ...base, ...groupObj };
|
||||
formApiRef.current?.setValue('endpoints', JSON.stringify(merged, null, 2));
|
||||
} catch (e) {
|
||||
extraFooter={
|
||||
endpointGroups.length > 0 && (
|
||||
<Space wrap>
|
||||
{endpointGroups.map((group) => (
|
||||
<Button
|
||||
key={group.id}
|
||||
size='small'
|
||||
type='primary'
|
||||
onClick={() => {
|
||||
try {
|
||||
const groupObj = typeof group.items === 'string' ? JSON.parse(group.items || '{}') : (group.items || {});
|
||||
formApiRef.current?.setValue('endpoints', JSON.stringify(groupObj, null, 2));
|
||||
} catch { }
|
||||
}
|
||||
}}
|
||||
>
|
||||
{group.name}
|
||||
</Button>
|
||||
))}
|
||||
</Space>
|
||||
)}
|
||||
const current =
|
||||
formApiRef.current?.getValue(
|
||||
'endpoints',
|
||||
) || '';
|
||||
let base = {};
|
||||
if (current && current.trim())
|
||||
base = JSON.parse(current);
|
||||
const groupObj =
|
||||
typeof group.items === 'string'
|
||||
? JSON.parse(group.items || '{}')
|
||||
: group.items || {};
|
||||
const merged = { ...base, ...groupObj };
|
||||
formApiRef.current?.setValue(
|
||||
'endpoints',
|
||||
JSON.stringify(merged, null, 2),
|
||||
);
|
||||
} catch (e) {
|
||||
try {
|
||||
const groupObj =
|
||||
typeof group.items === 'string'
|
||||
? JSON.parse(group.items || '{}')
|
||||
: group.items || {};
|
||||
formApiRef.current?.setValue(
|
||||
'endpoints',
|
||||
JSON.stringify(groupObj, null, 2),
|
||||
);
|
||||
} catch {}
|
||||
}
|
||||
}}
|
||||
>
|
||||
{group.name}
|
||||
</Button>
|
||||
))}
|
||||
</Space>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Form.Switch
|
||||
field='status'
|
||||
label={t('状态')}
|
||||
size="large"
|
||||
size='large'
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
@@ -464,4 +522,4 @@ const EditModelModal = (props) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default EditModelModal;
|
||||
export default EditModelModal;
|
||||
|
||||
@@ -32,11 +32,7 @@ import {
|
||||
Avatar,
|
||||
Spin,
|
||||
} from '@douyinfe/semi-ui';
|
||||
import {
|
||||
IconLayers,
|
||||
IconSave,
|
||||
IconClose,
|
||||
} from '@douyinfe/semi-icons';
|
||||
import { IconLayers, IconSave, IconClose } from '@douyinfe/semi-icons';
|
||||
import { API, showError, showSuccess } from '../../../../helpers';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useIsMobile } from '../../../../hooks/common/useIsMobile';
|
||||
@@ -53,7 +49,12 @@ const ENDPOINT_TEMPLATE = {
|
||||
'image-generation': { path: '/v1/images/generations', method: 'POST' },
|
||||
};
|
||||
|
||||
const EditPrefillGroupModal = ({ visible, onClose, editingGroup, onSuccess }) => {
|
||||
const EditPrefillGroupModal = ({
|
||||
visible,
|
||||
onClose,
|
||||
editingGroup,
|
||||
onSuccess,
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const isMobile = useIsMobile();
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -112,7 +113,7 @@ const EditPrefillGroupModal = ({ visible, onClose, editingGroup, onSuccess }) =>
|
||||
|
||||
return (
|
||||
<SideSheet
|
||||
placement="left"
|
||||
placement='left'
|
||||
title={
|
||||
<Space>
|
||||
{isEdit ? (
|
||||
@@ -193,13 +194,15 @@ const EditPrefillGroupModal = ({ visible, onClose, editingGroup, onSuccess }) =>
|
||||
</Avatar>
|
||||
<div>
|
||||
<Text className='text-lg font-medium'>{t('基本信息')}</Text>
|
||||
<div className='text-xs text-gray-600'>{t('设置预填组的基本信息')}</div>
|
||||
<div className='text-xs text-gray-600'>
|
||||
{t('设置预填组的基本信息')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Row gutter={12}>
|
||||
<Col span={24}>
|
||||
<Form.Input
|
||||
field="name"
|
||||
field='name'
|
||||
label={t('组名')}
|
||||
placeholder={t('请输入组名')}
|
||||
rules={[{ required: true, message: t('请输入组名') }]}
|
||||
@@ -208,7 +211,7 @@ const EditPrefillGroupModal = ({ visible, onClose, editingGroup, onSuccess }) =>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Form.Select
|
||||
field="type"
|
||||
field='type'
|
||||
label={t('类型')}
|
||||
placeholder={t('选择组类型')}
|
||||
optionList={typeOptions}
|
||||
@@ -219,7 +222,7 @@ const EditPrefillGroupModal = ({ visible, onClose, editingGroup, onSuccess }) =>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Form.TextArea
|
||||
field="description"
|
||||
field='description'
|
||||
label={t('描述')}
|
||||
placeholder={t('请输入组描述')}
|
||||
rows={3}
|
||||
@@ -229,19 +232,28 @@ const EditPrefillGroupModal = ({ visible, onClose, editingGroup, onSuccess }) =>
|
||||
<Col span={24}>
|
||||
{selectedType === 'endpoint' ? (
|
||||
<JSONEditor
|
||||
field="items"
|
||||
field='items'
|
||||
label={t('端点映射')}
|
||||
value={formRef.current?.getValue('items') ?? (typeof editingGroup?.items === 'string' ? editingGroup.items : JSON.stringify(editingGroup.items || {}, null, 2))}
|
||||
onChange={(val) => formRef.current?.setValue('items', val)}
|
||||
value={
|
||||
formRef.current?.getValue('items') ??
|
||||
(typeof editingGroup?.items === 'string'
|
||||
? editingGroup.items
|
||||
: JSON.stringify(editingGroup.items || {}, null, 2))
|
||||
}
|
||||
onChange={(val) =>
|
||||
formRef.current?.setValue('items', val)
|
||||
}
|
||||
editorType='object'
|
||||
placeholder={'{\n "openai": {"path": "/v1/chat/completions", "method": "POST"}\n}'}
|
||||
placeholder={
|
||||
'{\n "openai": {"path": "/v1/chat/completions", "method": "POST"}\n}'
|
||||
}
|
||||
template={ENDPOINT_TEMPLATE}
|
||||
templateLabel={t('填入模板')}
|
||||
extraText={t('键为端点类型,值为路径和方法对象')}
|
||||
/>
|
||||
) : (
|
||||
<Form.TagInput
|
||||
field="items"
|
||||
field='items'
|
||||
label={t('项目')}
|
||||
placeholder={t('输入项目名称,按回车添加')}
|
||||
addOnBlur
|
||||
@@ -259,4 +271,4 @@ const EditPrefillGroupModal = ({ visible, onClose, editingGroup, onSuccess }) =>
|
||||
);
|
||||
};
|
||||
|
||||
export default EditPrefillGroupModal;
|
||||
export default EditPrefillGroupModal;
|
||||
|
||||
@@ -18,12 +18,7 @@ For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import {
|
||||
Modal,
|
||||
Form,
|
||||
Col,
|
||||
Row,
|
||||
} from '@douyinfe/semi-ui';
|
||||
import { Modal, Form, Col, Row } from '@douyinfe/semi-ui';
|
||||
import { API, showError, showSuccess } from '../../../../helpers';
|
||||
import { Typography } from '@douyinfe/semi-ui';
|
||||
import { IconLink } from '@douyinfe/semi-icons';
|
||||
@@ -138,7 +133,7 @@ const EditVendorModal = ({ visible, handleClose, refresh, editingVendor }) => {
|
||||
<Row gutter={12}>
|
||||
<Col span={24}>
|
||||
<Form.Input
|
||||
field="name"
|
||||
field='name'
|
||||
label={t('供应商名称')}
|
||||
placeholder={t('请输入供应商名称,如:OpenAI')}
|
||||
rules={[{ required: true, message: t('请输入供应商名称') }]}
|
||||
@@ -147,7 +142,7 @@ const EditVendorModal = ({ visible, handleClose, refresh, editingVendor }) => {
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Form.TextArea
|
||||
field="description"
|
||||
field='description'
|
||||
label={t('描述')}
|
||||
placeholder={t('请输入供应商描述')}
|
||||
rows={3}
|
||||
@@ -156,14 +151,19 @@ const EditVendorModal = ({ visible, handleClose, refresh, editingVendor }) => {
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Form.Input
|
||||
field="icon"
|
||||
field='icon'
|
||||
label={t('供应商图标')}
|
||||
placeholder={t("请输入图标名称")}
|
||||
placeholder={t('请输入图标名称')}
|
||||
extraText={
|
||||
<span>
|
||||
{t('图标使用@lobehub/icons库,如:OpenAI、Claude.Color,支持链式参数:OpenAI.Avatar.type={\'platform\'}、OpenRouter.Avatar.shape={\'square\'},查询所有可用图标请 ')}
|
||||
{t(
|
||||
"图标使用@lobehub/icons库,如:OpenAI、Claude.Color,支持链式参数:OpenAI.Avatar.type={'platform'}、OpenRouter.Avatar.shape={'square'},查询所有可用图标请 ",
|
||||
)}
|
||||
<Typography.Text
|
||||
link={{ href: 'https://icons.lobehub.com/components/lobe-hub', target: '_blank' }}
|
||||
link={{
|
||||
href: 'https://icons.lobehub.com/components/lobe-hub',
|
||||
target: '_blank',
|
||||
}}
|
||||
icon={<IconLink />}
|
||||
underline
|
||||
>
|
||||
@@ -175,11 +175,7 @@ const EditVendorModal = ({ visible, handleClose, refresh, editingVendor }) => {
|
||||
/>
|
||||
</Col>
|
||||
<Col span={24}>
|
||||
<Form.Switch
|
||||
field="status"
|
||||
label={t('状态')}
|
||||
initValue={true}
|
||||
/>
|
||||
<Form.Switch field='status' label={t('状态')} initValue={true} />
|
||||
</Col>
|
||||
</Row>
|
||||
</Form>
|
||||
@@ -187,4 +183,4 @@ const EditVendorModal = ({ visible, handleClose, refresh, editingVendor }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default EditVendorModal;
|
||||
export default EditVendorModal;
|
||||
|
||||
@@ -18,19 +18,25 @@ For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { Modal, Table, Spin, Button, Typography, Empty, Input } from '@douyinfe/semi-ui';
|
||||
import { IllustrationNoResult, IllustrationNoResultDark } from '@douyinfe/semi-illustrations';
|
||||
import {
|
||||
Modal,
|
||||
Table,
|
||||
Spin,
|
||||
Button,
|
||||
Typography,
|
||||
Empty,
|
||||
Input,
|
||||
} from '@douyinfe/semi-ui';
|
||||
import {
|
||||
IllustrationNoResult,
|
||||
IllustrationNoResultDark,
|
||||
} from '@douyinfe/semi-illustrations';
|
||||
import { IconSearch } from '@douyinfe/semi-icons';
|
||||
import { API, showError } from '../../../../helpers';
|
||||
import { MODEL_TABLE_PAGE_SIZE } from '../../../../constants';
|
||||
import { useIsMobile } from '../../../../hooks/common/useIsMobile';
|
||||
|
||||
const MissingModelsModal = ({
|
||||
visible,
|
||||
onClose,
|
||||
onConfigureModel,
|
||||
t,
|
||||
}) => {
|
||||
const MissingModelsModal = ({ visible, onClose, onConfigureModel, t }) => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [missingModels, setMissingModels] = useState([]);
|
||||
const [searchKeyword, setSearchKeyword] = useState('');
|
||||
@@ -64,7 +70,7 @@ const MissingModelsModal = ({
|
||||
|
||||
// 过滤和分页逻辑
|
||||
const filteredModels = missingModels.filter((model) =>
|
||||
model.toLowerCase().includes(searchKeyword.toLowerCase())
|
||||
model.toLowerCase().includes(searchKeyword.toLowerCase()),
|
||||
);
|
||||
|
||||
const dataSource = (() => {
|
||||
@@ -81,10 +87,10 @@ const MissingModelsModal = ({
|
||||
title: t('模型名称'),
|
||||
dataIndex: 'model',
|
||||
render: (text) => (
|
||||
<div className="flex items-center">
|
||||
<div className='flex items-center'>
|
||||
<Typography.Text strong>{text}</Typography.Text>
|
||||
</div>
|
||||
)
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
@@ -93,25 +99,28 @@ const MissingModelsModal = ({
|
||||
width: 100,
|
||||
render: (text, record) => (
|
||||
<Button
|
||||
type="primary"
|
||||
size="small"
|
||||
type='primary'
|
||||
size='small'
|
||||
onClick={() => onConfigureModel(record.model)}
|
||||
>
|
||||
{t('配置')}
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Modal
|
||||
title={
|
||||
<div className="flex flex-col gap-2 w-full">
|
||||
<div className="flex items-center gap-2">
|
||||
<Typography.Text strong className="!text-[var(--semi-color-text-0)] !text-base">
|
||||
<div className='flex flex-col gap-2 w-full'>
|
||||
<div className='flex items-center gap-2'>
|
||||
<Typography.Text
|
||||
strong
|
||||
className='!text-[var(--semi-color-text-0)] !text-base'
|
||||
>
|
||||
{t('未配置的模型列表')}
|
||||
</Typography.Text>
|
||||
<Typography.Text type="tertiary" size="small">
|
||||
<Typography.Text type='tertiary' size='small'>
|
||||
{t('共')} {missingModels.length} {t('个未配置模型')}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
@@ -121,20 +130,22 @@ const MissingModelsModal = ({
|
||||
onCancel={onClose}
|
||||
footer={null}
|
||||
size={isMobile ? 'full-width' : 'medium'}
|
||||
className="!rounded-lg"
|
||||
className='!rounded-lg'
|
||||
>
|
||||
<Spin spinning={loading}>
|
||||
{missingModels.length === 0 && !loading ? (
|
||||
<Empty
|
||||
image={<IllustrationNoResult style={{ width: 150, height: 150 }} />}
|
||||
darkModeImage={<IllustrationNoResultDark style={{ width: 150, height: 150 }} />}
|
||||
darkModeImage={
|
||||
<IllustrationNoResultDark style={{ width: 150, height: 150 }} />
|
||||
}
|
||||
description={t('暂无缺失模型')}
|
||||
style={{ padding: 30 }}
|
||||
/>
|
||||
) : (
|
||||
<div className="missing-models-content">
|
||||
<div className='missing-models-content'>
|
||||
{/* 搜索框 */}
|
||||
<div className="flex items-center justify-end gap-2 w-full mb-4">
|
||||
<div className='flex items-center justify-end gap-2 w-full mb-4'>
|
||||
<Input
|
||||
placeholder={t('搜索模型...')}
|
||||
value={searchKeyword}
|
||||
@@ -142,7 +153,7 @@ const MissingModelsModal = ({
|
||||
setSearchKeyword(v);
|
||||
setCurrentPage(1);
|
||||
}}
|
||||
className="!w-full"
|
||||
className='!w-full'
|
||||
prefix={<IconSearch />}
|
||||
showClear
|
||||
/>
|
||||
@@ -163,9 +174,17 @@ const MissingModelsModal = ({
|
||||
/>
|
||||
) : (
|
||||
<Empty
|
||||
image={<IllustrationNoResult style={{ width: 100, height: 100 }} />}
|
||||
darkModeImage={<IllustrationNoResultDark style={{ width: 100, height: 100 }} />}
|
||||
description={searchKeyword ? t('未找到匹配的模型') : t('暂无缺失模型')}
|
||||
image={
|
||||
<IllustrationNoResult style={{ width: 100, height: 100 }} />
|
||||
}
|
||||
darkModeImage={
|
||||
<IllustrationNoResultDark
|
||||
style={{ width: 100, height: 100 }}
|
||||
/>
|
||||
}
|
||||
description={
|
||||
searchKeyword ? t('未找到匹配的模型') : t('暂无缺失模型')
|
||||
}
|
||||
style={{ padding: 20 }}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -30,20 +30,25 @@ import {
|
||||
Spin,
|
||||
Empty,
|
||||
} from '@douyinfe/semi-ui';
|
||||
import {
|
||||
IconPlus,
|
||||
IconLayers,
|
||||
} from '@douyinfe/semi-icons';
|
||||
import { IconPlus, IconLayers } from '@douyinfe/semi-icons';
|
||||
import {
|
||||
IllustrationNoResult,
|
||||
IllustrationNoResultDark,
|
||||
} from '@douyinfe/semi-illustrations';
|
||||
import { API, showError, showSuccess, stringToColor } from '../../../../helpers';
|
||||
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';
|
||||
import {
|
||||
renderLimitedItems,
|
||||
renderDescription,
|
||||
} from '../../../common/ui/RenderUtils';
|
||||
|
||||
const { Text, Title } = Typography;
|
||||
|
||||
@@ -121,8 +126,9 @@ const PrefillGroupManagement = ({ visible, onClose }) => {
|
||||
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 color='white' shape='circle' size='small'>
|
||||
{typeOptions.find((opt) => opt.value === record.type)?.label ||
|
||||
record.type}
|
||||
</Tag>
|
||||
</Space>
|
||||
),
|
||||
@@ -140,34 +146,49 @@ const PrefillGroupManagement = ({ visible, onClose }) => {
|
||||
render: (items, record) => {
|
||||
try {
|
||||
if (record.type === 'endpoint') {
|
||||
const obj = typeof items === 'string' ? JSON.parse(items || '{}') : (items || {});
|
||||
const obj =
|
||||
typeof items === 'string'
|
||||
? JSON.parse(items || '{}')
|
||||
: items || {};
|
||||
const keys = Object.keys(obj);
|
||||
if (keys.length === 0) return <Text type="tertiary">{t('暂无项目')}</Text>;
|
||||
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)}>
|
||||
<Tag
|
||||
key={idx}
|
||||
size='small'
|
||||
shape='circle'
|
||||
color={stringToColor(key)}
|
||||
>
|
||||
{key}
|
||||
</Tag>
|
||||
),
|
||||
maxDisplay: 3,
|
||||
});
|
||||
}
|
||||
const itemsArray = typeof items === 'string' ? JSON.parse(items) : items;
|
||||
const itemsArray =
|
||||
typeof items === 'string' ? JSON.parse(items) : items;
|
||||
if (!Array.isArray(itemsArray) || itemsArray.length === 0) {
|
||||
return <Text type="tertiary">{t('暂无项目')}</Text>;
|
||||
return <Text type='tertiary'>{t('暂无项目')}</Text>;
|
||||
}
|
||||
return renderLimitedItems({
|
||||
items: itemsArray,
|
||||
renderItem: (item, idx) => (
|
||||
<Tag key={idx} size="small" shape='circle' color={stringToColor(item)}>
|
||||
<Tag
|
||||
key={idx}
|
||||
size='small'
|
||||
shape='circle'
|
||||
color={stringToColor(item)}
|
||||
>
|
||||
{item}
|
||||
</Tag>
|
||||
),
|
||||
maxDisplay: 3,
|
||||
});
|
||||
} catch {
|
||||
return <Text type="tertiary">{t('数据格式错误')}</Text>;
|
||||
return <Text type='tertiary'>{t('数据格式错误')}</Text>;
|
||||
}
|
||||
},
|
||||
},
|
||||
@@ -178,20 +199,14 @@ const PrefillGroupManagement = ({ visible, onClose }) => {
|
||||
width: 140,
|
||||
render: (_, record) => (
|
||||
<Space>
|
||||
<Button
|
||||
size="small"
|
||||
onClick={() => handleEdit(record)}
|
||||
>
|
||||
<Button size='small' onClick={() => handleEdit(record)}>
|
||||
{t('编辑')}
|
||||
</Button>
|
||||
<Popconfirm
|
||||
title={t('确定删除此组?')}
|
||||
onConfirm={() => deleteGroup(record.id)}
|
||||
>
|
||||
<Button
|
||||
size="small"
|
||||
type="danger"
|
||||
>
|
||||
<Button size='small' type='danger'>
|
||||
{t('删除')}
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
@@ -209,7 +224,7 @@ const PrefillGroupManagement = ({ visible, onClose }) => {
|
||||
return (
|
||||
<>
|
||||
<SideSheet
|
||||
placement="left"
|
||||
placement='left'
|
||||
title={
|
||||
<Space>
|
||||
<Tag color='blue' shape='circle'>
|
||||
@@ -235,14 +250,16 @@ const PrefillGroupManagement = ({ visible, onClose }) => {
|
||||
</Avatar>
|
||||
<div>
|
||||
<Text className='text-lg font-medium'>{t('组列表')}</Text>
|
||||
<div className='text-xs text-gray-600'>{t('管理模型、标签、端点等预填组')}</div>
|
||||
<div className='text-xs text-gray-600'>
|
||||
{t('管理模型、标签、端点等预填组')}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-end mb-4">
|
||||
<div className='flex justify-end mb-4'>
|
||||
<Button
|
||||
type="primary"
|
||||
type='primary'
|
||||
theme='solid'
|
||||
size="small"
|
||||
size='small'
|
||||
icon={<IconPlus />}
|
||||
onClick={() => handleEdit()}
|
||||
>
|
||||
@@ -253,15 +270,21 @@ const PrefillGroupManagement = ({ visible, onClose }) => {
|
||||
<CardTable
|
||||
columns={columns}
|
||||
dataSource={groups}
|
||||
rowKey="id"
|
||||
rowKey='id'
|
||||
hidePagination={true}
|
||||
size="small"
|
||||
size='small'
|
||||
scroll={{ x: 'max-content' }}
|
||||
/>
|
||||
) : (
|
||||
<Empty
|
||||
image={<IllustrationNoResult style={{ width: 150, height: 150 }} />}
|
||||
darkModeImage={<IllustrationNoResultDark style={{ width: 150, height: 150 }} />}
|
||||
image={
|
||||
<IllustrationNoResult style={{ width: 150, height: 150 }} />
|
||||
}
|
||||
darkModeImage={
|
||||
<IllustrationNoResultDark
|
||||
style={{ width: 150, height: 150 }}
|
||||
/>
|
||||
}
|
||||
description={t('暂无预填组')}
|
||||
style={{ padding: 30 }}
|
||||
/>
|
||||
@@ -282,4 +305,4 @@ const PrefillGroupManagement = ({ visible, onClose }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default PrefillGroupManagement;
|
||||
export default PrefillGroupManagement;
|
||||
|
||||
Reference in New Issue
Block a user