| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import React from 'react'; |
| import { |
| Modal, |
| Button, |
| Input, |
| Table, |
| Tag, |
| Typography, |
| Select, |
| } from '@douyinfe/semi-ui'; |
| import { IconSearch } from '@douyinfe/semi-icons'; |
| import { copy, showError, showInfo, showSuccess } from '../../../../helpers'; |
| import { MODEL_TABLE_PAGE_SIZE } from '../../../../constants'; |
|
|
| const ModelTestModal = ({ |
| showModelTestModal, |
| currentTestChannel, |
| handleCloseModal, |
| isBatchTesting, |
| batchTestModels, |
| modelSearchKeyword, |
| setModelSearchKeyword, |
| selectedModelKeys, |
| setSelectedModelKeys, |
| modelTestResults, |
| testingModels, |
| testChannel, |
| modelTablePage, |
| setModelTablePage, |
| selectedEndpointType, |
| setSelectedEndpointType, |
| allSelectingRef, |
| isMobile, |
| t, |
| }) => { |
| const hasChannel = Boolean(currentTestChannel); |
|
|
| const filteredModels = hasChannel |
| ? currentTestChannel.models |
| .split(',') |
| .filter((model) => |
| model.toLowerCase().includes(modelSearchKeyword.toLowerCase()), |
| ) |
| : []; |
|
|
| const endpointTypeOptions = [ |
| { value: '', label: t('自动检测') }, |
| { value: 'openai', label: 'OpenAI (/v1/chat/completions)' }, |
| { value: 'openai-response', label: 'OpenAI Response (/v1/responses)' }, |
| { value: 'anthropic', label: 'Anthropic (/v1/messages)' }, |
| { |
| value: 'gemini', |
| label: 'Gemini (/v1beta/models/{model}:generateContent)', |
| }, |
| { value: 'jina-rerank', label: 'Jina Rerank (/rerank)' }, |
| { |
| value: 'image-generation', |
| label: t('图像生成') + ' (/v1/images/generations)', |
| }, |
| { value: 'embeddings', label: 'Embeddings (/v1/embeddings)' }, |
| ]; |
|
|
| 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, |
| selectedEndpointType, |
| ) |
| } |
| loading={isTesting} |
| size='small' |
| > |
| {t('测试')} |
| </Button> |
| ); |
| }, |
| }, |
| ]; |
|
|
| const dataSource = (() => { |
| if (!hasChannel) return []; |
| 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={ |
| hasChannel ? ( |
| <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' size='small'> |
| {t('共')} {currentTestChannel.models.split(',').length}{' '} |
| {t('个模型')} |
| </Typography.Text> |
| </div> |
| </div> |
| ) : null |
| } |
| visible={showModelTestModal} |
| onCancel={handleCloseModal} |
| footer={ |
| hasChannel ? ( |
| <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> |
| ) : null |
| } |
| maskClosable={!isBatchTesting} |
| className='!rounded-lg' |
| size={isMobile ? 'full-width' : 'large'} |
| > |
| {hasChannel && ( |
| <div className='model-test-scroll'> |
| {/* 端点类型选择器 */} |
| <div className='flex items-center gap-2 w-full mb-2'> |
| <Typography.Text strong>{t('端点类型')}:</Typography.Text> |
| <Select |
| value={selectedEndpointType} |
| onChange={setSelectedEndpointType} |
| optionList={endpointTypeOptions} |
| className='!w-full' |
| placeholder={t('选择端点类型')} |
| /> |
| </div> |
| |
| {/* 搜索与操作按钮 */} |
| <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; |
|
|