Commit 23206840 by Seefs Committed by GitHub

Merge pull request #2610 from Bliod-Cook/main

parents 5e23d5d6 67d6699c
...@@ -60,6 +60,7 @@ const JSONEditor = ({ ...@@ -60,6 +60,7 @@ const JSONEditor = ({
editorType = 'keyValue', editorType = 'keyValue',
rules = [], rules = [],
formApi = null, formApi = null,
renderStringValueSuffix,
...props ...props
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
...@@ -335,7 +336,7 @@ const JSONEditor = ({ ...@@ -335,7 +336,7 @@ const JSONEditor = ({
]); ]);
// 渲染值输入控件(支持嵌套) // 渲染值输入控件(支持嵌套)
const renderValueInput = (pairId, value) => { const renderValueInput = (pairId, pairKey, value) => {
const valueType = typeof value; const valueType = typeof value;
if (valueType === 'boolean') { if (valueType === 'boolean') {
...@@ -387,6 +388,7 @@ const JSONEditor = ({ ...@@ -387,6 +388,7 @@ const JSONEditor = ({
<Input <Input
placeholder={t('参数值')} placeholder={t('参数值')}
value={String(value)} value={String(value)}
suffix={renderStringValueSuffix?.({ pairId, pairKey, value })}
onChange={(newValue) => { onChange={(newValue) => {
let convertedValue = newValue; let convertedValue = newValue;
if (newValue === 'true') convertedValue = true; if (newValue === 'true') convertedValue = true;
...@@ -470,7 +472,9 @@ const JSONEditor = ({ ...@@ -470,7 +472,9 @@ const JSONEditor = ({
)} )}
</div> </div>
</Col> </Col>
<Col span={12}>{renderValueInput(pair.id, pair.value)}</Col> <Col span={12}>
{renderValueInput(pair.id, pair.key, pair.value)}
</Col>
<Col span={2}> <Col span={2}>
<Button <Button
icon={<IconDelete />} icon={<IconDelete />}
......
/*
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, { useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useIsMobile } from '../../../../hooks/common/useIsMobile';
import {
Collapse,
Empty,
Input,
Modal,
Radio,
Typography,
} from '@douyinfe/semi-ui';
import {
IllustrationNoResult,
IllustrationNoResultDark,
} from '@douyinfe/semi-illustrations';
import { IconSearch } from '@douyinfe/semi-icons';
import { getModelCategories } from '../../../../helpers/render';
const SingleModelSelectModal = ({
visible,
models = [],
selected = '',
onConfirm,
onCancel,
}) => {
const { t } = useTranslation();
const isMobile = useIsMobile();
const normalizeModelName = (model) => String(model ?? '').trim();
const normalizedModels = useMemo(() => {
const list = Array.isArray(models) ? models : [];
return Array.from(new Set(list.map(normalizeModelName).filter(Boolean)));
}, [models]);
const [keyword, setKeyword] = useState('');
const [selectedModel, setSelectedModel] = useState('');
useEffect(() => {
if (visible) {
setKeyword('');
setSelectedModel(normalizeModelName(selected));
}
}, [visible, selected]);
const filteredModels = useMemo(() => {
const lower = keyword.trim().toLowerCase();
if (!lower) return normalizedModels;
return normalizedModels.filter((m) => m.toLowerCase().includes(lower));
}, [normalizedModels, keyword]);
const modelsByCategory = useMemo(() => {
const categories = getModelCategories(t);
const categorized = {};
const uncategorized = [];
filteredModels.forEach((model) => {
let foundCategory = false;
for (const [key, category] of Object.entries(categories)) {
if (key !== 'all' && category.filter({ model_name: model })) {
if (!categorized[key]) {
categorized[key] = {
label: category.label,
icon: category.icon,
models: [],
};
}
categorized[key].models.push(model);
foundCategory = true;
break;
}
}
if (!foundCategory) {
uncategorized.push(model);
}
});
if (uncategorized.length > 0) {
categorized.other = {
label: t('其他'),
icon: null,
models: uncategorized,
};
}
return categorized;
}, [filteredModels, t]);
const categoryEntries = useMemo(
() => Object.entries(modelsByCategory),
[modelsByCategory],
);
return (
<Modal
header={
<div className='flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2 sm:gap-4 py-4'>
<Typography.Title heading={5} className='m-0'>
{t('选择模型')}
</Typography.Title>
</div>
}
visible={visible}
onOk={() => onConfirm?.(selectedModel)}
onCancel={onCancel}
okText={t('确定')}
cancelText={t('取消')}
okButtonProps={{ disabled: !selectedModel }}
size={isMobile ? 'full-width' : 'large'}
closeOnEsc
maskClosable
centered
>
<Input
prefix={<IconSearch size={14} />}
placeholder={t('搜索模型')}
value={keyword}
onChange={(v) => setKeyword(v)}
showClear
/>
<div style={{ maxHeight: 400, overflowY: 'auto', paddingRight: 8 }}>
{filteredModels.length === 0 ? (
<Empty
image={<IllustrationNoResult style={{ width: 150, height: 150 }} />}
darkModeImage={
<IllustrationNoResultDark style={{ width: 150, height: 150 }} />
}
description={t('暂无匹配模型')}
style={{ padding: 30 }}
/>
) : (
<Radio.Group
className='w-full'
style={{ width: '100%' }}
value={selectedModel}
onChange={(val) => {
const next = val && val.target ? val.target.value : val;
setSelectedModel(next);
}}
>
<Collapse
className='w-full'
style={{ width: '100%' }}
defaultActiveKey={[]}
>
{categoryEntries.map(([key, categoryData], index) => (
<Collapse.Panel
key={`${key}_${index}`}
itemKey={`${key}_${index}`}
header={
<span className='flex items-center gap-2'>
{categoryData.icon}
<span>
{categoryData.label} ({categoryData.models.length})
</span>
</span>
}
>
<div className='grid grid-cols-2 gap-x-4'>
{categoryData.models.map((model) => (
<Radio key={model} value={model} className='my-1'>
{model}
</Radio>
))}
</div>
</Collapse.Panel>
))}
</Collapse>
</Radio.Group>
)}
</div>
</Modal>
);
};
export default SingleModelSelectModal;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment