🚀 feat(web/channels): Deep modular refactor of Channels table
1. Split monolithic `ChannelsTable` (2200+ LOC) into focused components
• `channels/index.jsx` – composition entry
• `ChannelsTable.jsx` – pure `<Table>` rendering
• `ChannelsActions.jsx` – bulk & settings toolbar
• `ChannelsFilters.jsx` – search / create / column-settings form
• `ChannelsTabs.jsx` – type tabs
• `ChannelsColumnDefs.js` – column definitions & render helpers
• `modals/` – BatchTag, ColumnSelector, ModelTest modals
2. Extract domain hook
• Moved `useChannelsData.js` → `src/hooks/channels/useChannelsData.js`
– centralises state, API calls, pagination, filters, batch ops
– now exports `setActivePage`, fixing tab / status switch errors
3. Update wiring
• All sub-components consume data via `useChannelsData` props
• Adjusted import paths after hook relocation
4. Clean legacy file
• Legacy `components/table/ChannelsTable.js` now re-exports new module
5. Bug fixes
• Tab switching, status filter & tag aggregation restored
• Column selector & batch actions operate via unified hook
This commit completes the first phase of modularising the Channels feature, laying groundwork for consistent, maintainable table architecture across the app.
This commit is contained in:
256
web/src/components/table/channels/modals/ModelTestModal.jsx
Normal file
256
web/src/components/table/channels/modals/ModelTestModal.jsx
Normal file
@@ -0,0 +1,256 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Modal,
|
||||
Button,
|
||||
Input,
|
||||
Table,
|
||||
Tag,
|
||||
Typography
|
||||
} from '@douyinfe/semi-ui';
|
||||
import { IconSearch } from '@douyinfe/semi-icons';
|
||||
import { copy, showError, showInfo, showSuccess } from '../../../../helpers/index.js';
|
||||
import { MODEL_TABLE_PAGE_SIZE } from '../../../../constants/index.js';
|
||||
|
||||
const ModelTestModal = ({
|
||||
showModelTestModal,
|
||||
currentTestChannel,
|
||||
handleCloseModal,
|
||||
isBatchTesting,
|
||||
batchTestModels,
|
||||
modelSearchKeyword,
|
||||
setModelSearchKeyword,
|
||||
selectedModelKeys,
|
||||
setSelectedModelKeys,
|
||||
modelTestResults,
|
||||
testingModels,
|
||||
testChannel,
|
||||
modelTablePage,
|
||||
setModelTablePage,
|
||||
allSelectingRef,
|
||||
isMobile,
|
||||
t
|
||||
}) => {
|
||||
if (!showModelTestModal || !currentTestChannel) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const filteredModels = currentTestChannel.models
|
||||
.split(',')
|
||||
.filter((model) =>
|
||||
model.toLowerCase().includes(modelSearchKeyword.toLowerCase())
|
||||
);
|
||||
|
||||
const handleCopySelected = () => {
|
||||
if (selectedModelKeys.length === 0) {
|
||||
showError(t('请先选择模型!'));
|
||||
return;
|
||||
}
|
||||
copy(selectedModelKeys.join(',')).then((ok) => {
|
||||
if (ok) {
|
||||
showSuccess(t('已复制 ${count} 个模型').replace('${count}', selectedModelKeys.length));
|
||||
} else {
|
||||
showError(t('复制失败,请手动复制'));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleSelectSuccess = () => {
|
||||
if (!currentTestChannel) return;
|
||||
const successKeys = currentTestChannel.models
|
||||
.split(',')
|
||||
.filter((m) => m.toLowerCase().includes(modelSearchKeyword.toLowerCase()))
|
||||
.filter((m) => {
|
||||
const result = modelTestResults[`${currentTestChannel.id}-${m}`];
|
||||
return result && result.success;
|
||||
});
|
||||
if (successKeys.length === 0) {
|
||||
showInfo(t('暂无成功模型'));
|
||||
}
|
||||
setSelectedModelKeys(successKeys);
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: t('模型名称'),
|
||||
dataIndex: 'model',
|
||||
render: (text) => (
|
||||
<div className="flex items-center">
|
||||
<Typography.Text strong>{text}</Typography.Text>
|
||||
</div>
|
||||
)
|
||||
},
|
||||
{
|
||||
title: t('状态'),
|
||||
dataIndex: 'status',
|
||||
render: (text, record) => {
|
||||
const testResult = modelTestResults[`${currentTestChannel.id}-${record.model}`];
|
||||
const isTesting = testingModels.has(record.model);
|
||||
|
||||
if (isTesting) {
|
||||
return (
|
||||
<Tag color='blue' shape='circle'>
|
||||
{t('测试中')}
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
|
||||
if (!testResult) {
|
||||
return (
|
||||
<Tag color='grey' shape='circle'>
|
||||
{t('未开始')}
|
||||
</Tag>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
<Tag
|
||||
color={testResult.success ? 'green' : 'red'}
|
||||
shape='circle'
|
||||
>
|
||||
{testResult.success ? t('成功') : t('失败')}
|
||||
</Tag>
|
||||
{testResult.success && (
|
||||
<Typography.Text type="tertiary">
|
||||
{t('请求时长: ${time}s').replace('${time}', testResult.time.toFixed(2))}
|
||||
</Typography.Text>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'operate',
|
||||
render: (text, record) => {
|
||||
const isTesting = testingModels.has(record.model);
|
||||
return (
|
||||
<Button
|
||||
type='tertiary'
|
||||
onClick={() => testChannel(currentTestChannel, record.model)}
|
||||
loading={isTesting}
|
||||
size='small'
|
||||
>
|
||||
{t('测试')}
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
const dataSource = (() => {
|
||||
const start = (modelTablePage - 1) * MODEL_TABLE_PAGE_SIZE;
|
||||
const end = start + MODEL_TABLE_PAGE_SIZE;
|
||||
return filteredModels.slice(start, end).map((model) => ({
|
||||
model,
|
||||
key: model,
|
||||
}));
|
||||
})();
|
||||
|
||||
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">
|
||||
{currentTestChannel.name} {t('渠道的模型测试')}
|
||||
</Typography.Text>
|
||||
<Typography.Text type="tertiary" className="!text-xs flex items-center">
|
||||
{t('共')} {currentTestChannel.models.split(',').length} {t('个模型')}
|
||||
</Typography.Text>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
visible={showModelTestModal}
|
||||
onCancel={handleCloseModal}
|
||||
footer={
|
||||
<div className="flex justify-end">
|
||||
{isBatchTesting ? (
|
||||
<Button
|
||||
type='danger'
|
||||
onClick={handleCloseModal}
|
||||
>
|
||||
{t('停止测试')}
|
||||
</Button>
|
||||
) : (
|
||||
<Button
|
||||
type='tertiary'
|
||||
onClick={handleCloseModal}
|
||||
>
|
||||
{t('取消')}
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
onClick={batchTestModels}
|
||||
loading={isBatchTesting}
|
||||
disabled={isBatchTesting}
|
||||
>
|
||||
{isBatchTesting ? t('测试中...') : t('批量测试${count}个模型').replace(
|
||||
'${count}',
|
||||
filteredModels.length
|
||||
)}
|
||||
</Button>
|
||||
</div>
|
||||
}
|
||||
maskClosable={!isBatchTesting}
|
||||
className="!rounded-lg"
|
||||
size={isMobile ? 'full-width' : 'large'}
|
||||
>
|
||||
<div className="model-test-scroll">
|
||||
{/* 搜索与操作按钮 */}
|
||||
<div className="flex items-center justify-end gap-2 w-full mb-2">
|
||||
<Input
|
||||
placeholder={t('搜索模型...')}
|
||||
value={modelSearchKeyword}
|
||||
onChange={(v) => {
|
||||
setModelSearchKeyword(v);
|
||||
setModelTablePage(1);
|
||||
}}
|
||||
className="!w-full"
|
||||
prefix={<IconSearch />}
|
||||
showClear
|
||||
/>
|
||||
|
||||
<Button onClick={handleCopySelected}>
|
||||
{t('复制已选')}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
type='tertiary'
|
||||
onClick={handleSelectSuccess}
|
||||
>
|
||||
{t('选择成功')}
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Table
|
||||
columns={columns}
|
||||
dataSource={dataSource}
|
||||
rowSelection={{
|
||||
selectedRowKeys: selectedModelKeys,
|
||||
onChange: (keys) => {
|
||||
if (allSelectingRef.current) {
|
||||
allSelectingRef.current = false;
|
||||
return;
|
||||
}
|
||||
setSelectedModelKeys(keys);
|
||||
},
|
||||
onSelectAll: (checked) => {
|
||||
allSelectingRef.current = true;
|
||||
setSelectedModelKeys(checked ? filteredModels : []);
|
||||
},
|
||||
}}
|
||||
pagination={{
|
||||
currentPage: modelTablePage,
|
||||
pageSize: MODEL_TABLE_PAGE_SIZE,
|
||||
total: filteredModels.length,
|
||||
showSizeChanger: false,
|
||||
onPageChange: (page) => setModelTablePage(page),
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default ModelTestModal;
|
||||
Reference in New Issue
Block a user