Phase C4: 模型连接测试增强 - 多模态测试+批量测试+暗色主题修复
This commit is contained in:
1 parent
5828728f00
commit
6ccf6d6867
3 files changed
+392
-71
No files matched your search
@@ -94,8 +94,10 @@ const Models: React.FC = () => {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [modelSearch, setModelSearch] = useState('');
|
||||
const [testResult, setTestResult] = useState<TestResult | null>(null);
|
||||
const [testingModelId, setTestingModelId] = useState<string | null>(null);
|
||||
const [testResults, setTestResults] = useState<Record<string, TestResult>>({});
|
||||
const [testingIds, setTestingIds] = useState<Set<string>>(new Set());
|
||||
const [testingAll, setTestingAll] = useState(false);
|
||||
const [expandedTestResult, setExpandedTestResult] = useState<string | null>(null);
|
||||
const [showModelEditor, setShowModelEditor] = useState(false);
|
||||
const [editingModel, setEditingModel] = useState<EditingModel | null>(null);
|
||||
const [modelSaving, setModelSaving] = useState(false);
|
||||
@@ -147,20 +149,64 @@ const Models: React.FC = () => {
|
||||
);
|
||||
}, [models, modelSearch]);
|
||||
|
||||
// Test model connection
|
||||
// Test single model connection
|
||||
const handleTestConnection = async (modelId: string) => {
|
||||
setTestingModelId(modelId);
|
||||
setTestResult(null);
|
||||
setTestingIds(prev => new Set(prev).add(modelId));
|
||||
try {
|
||||
const result = await adminAPI.testModelConnection(modelId) as any;
|
||||
setTestResult(result.success !== undefined ? result : (result.data || result as TestResult));
|
||||
const testResult: TestResult = result.success !== undefined ? result : (result.data || result as TestResult);
|
||||
setTestResults(prev => ({ ...prev, [modelId]: testResult }));
|
||||
} catch (err: any) {
|
||||
setTestResult({ success: false, message: err.message || '测试失败' });
|
||||
setTestResults(prev => ({ ...prev, [modelId]: { success: false, message: err.message || '测试失败' } }));
|
||||
} finally {
|
||||
setTestingModelId(null);
|
||||
setTestingIds(prev => { const next = new Set(prev); next.delete(modelId); return next; });
|
||||
}
|
||||
};
|
||||
|
||||
// Test all models
|
||||
const handleTestAll = async () => {
|
||||
setTestingAll(true);
|
||||
setTestResults({});
|
||||
try {
|
||||
const result = await adminAPI.testAllModels() as any;
|
||||
const data = result.data || result;
|
||||
const resultsMap: Record<string, TestResult> = {};
|
||||
if (data.results) {
|
||||
for (const r of data.results) {
|
||||
resultsMap[r.model_id] = {
|
||||
success: r.success,
|
||||
message: r.message,
|
||||
latency_ms: r.latency_ms,
|
||||
};
|
||||
}
|
||||
}
|
||||
setTestResults(resultsMap);
|
||||
} catch (err: any) {
|
||||
setError('批量测试失败: ' + (err.message || ''));
|
||||
} finally {
|
||||
setTestingAll(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Get model type label
|
||||
const getModelType = (caps: string[] | undefined): string => {
|
||||
if (!caps || caps.length === 0) return 'text';
|
||||
const lower = caps.map(c => c.toLowerCase());
|
||||
if (lower.includes('image')) return 'image';
|
||||
if (lower.includes('audio')) return 'audio';
|
||||
if (lower.includes('video')) return 'video';
|
||||
if (lower.includes('vision')) return 'vision';
|
||||
return 'text';
|
||||
};
|
||||
|
||||
const modelTypeLabels: Record<string, { label: string; color: string }> = {
|
||||
text: { label: '文本', color: '#5B5FED' },
|
||||
image: { label: '图像生成', color: '#D9383A' },
|
||||
audio: { label: '音频', color: '#E8922C' },
|
||||
video: { label: '视频', color: '#EC4899' },
|
||||
vision: { label: '视觉理解', color: '#219B5E' },
|
||||
};
|
||||
|
||||
const handleSetDefault = async (modelId: string) => {
|
||||
try {
|
||||
await adminAPI.setDefaultModel(modelId);
|
||||
@@ -319,6 +365,11 @@ const Models: React.FC = () => {
|
||||
管理 AI 模型供应商、模型配置和连接测试
|
||||
</p>
|
||||
</div>
|
||||
<button onClick={handleTestAll}
|
||||
disabled={testingAll}
|
||||
style={btnStyle(testingAll ? '#4B5563' : '#059669')}>
|
||||
{testingAll ? '批量测试中...' : '⚡ 测试全部'}
|
||||
</button>
|
||||
<button onClick={() => { setShowAddProvider(true); setEditingProvider(null); setProviderForm({ name: '', protocol: 'openai_compatible', api_key: '', api_base_url: '' }); }}
|
||||
style={btnStyle('#5B5FED')}>
|
||||
+ 添加供应商
|
||||
@@ -426,7 +477,8 @@ const Models: React.FC = () => {
|
||||
<tr style={{ background: '#F3F4F6' }}>
|
||||
<th style={thStyle}>模型名称</th>
|
||||
<th style={thStyle}>显示名称</th>
|
||||
<th style={thStyle}>能力</th>
|
||||
<th style={thStyle}>类型</th>
|
||||
<th style={thStyle}>能力</th>
|
||||
<th style={thStyle}>状态</th>
|
||||
<th style={thStyle}>价格</th>
|
||||
<th style={thStyle}>调用量</th>
|
||||
@@ -446,6 +498,16 @@ const Models: React.FC = () => {
|
||||
</span>
|
||||
{model.isDefault && <span style={{ fontSize: '11px', color: '#16A34A', marginLeft: 6 }}>⭐ 默认</span>}
|
||||
</td>
|
||||
<td style={tdStyle}>
|
||||
<span style={{
|
||||
fontSize: '11px', padding: '2px 8px', borderRadius: '3px',
|
||||
background: (modelTypeLabels[getModelType(model.capabilities)].color) + '22',
|
||||
color: modelTypeLabels[getModelType(model.capabilities)].color,
|
||||
fontWeight: 500,
|
||||
}}>
|
||||
{modelTypeLabels[getModelType(model.capabilities)].label}
|
||||
</span>
|
||||
</td>
|
||||
<td style={tdStyle}>
|
||||
<div style={{ display: 'flex', gap: '4px', flexWrap: 'wrap' }}>
|
||||
{(model.capabilities || []).map(cap => (
|
||||
@@ -475,18 +537,81 @@ const Models: React.FC = () => {
|
||||
</td>
|
||||
<td style={tdStyle}>
|
||||
<div style={{ display: 'flex', gap: '4px', flexWrap: 'wrap' }}>
|
||||
<button onClick={() => handleTestConnection(model.id)} disabled={testingModelId === model.id}
|
||||
style={{ ...btnStyle(testingModelId === model.id ? '#9CA3AF' : '#059669', 'small') }}>
|
||||
{testingModelId === model.id ? '测试中...' : '测试'}
|
||||
<button onClick={() => handleTestConnection(model.id)} disabled={testingIds.has(model.id)}
|
||||
style={{ ...btnStyle(testingIds.has(model.id) ? '#4B5563' : '#059669', 'small') }}>
|
||||
{testingIds.has(model.id) ? '测试中...' : '测试'}
|
||||
</button>
|
||||
{testResult && testingModelId === null && (
|
||||
<span style={{ fontSize: '11px', padding: '4px 8px', background: testResult.success ? '#DCFCE7' : '#FEE2E2', color: testResult.success ? '#16A34A' : '#DC2626', borderRadius: '4px' }}>
|
||||
{testResult.success ? `✓ ${testResult.latency_ms || 0}ms` : '✗ 失败'}
|
||||
{testResults[model.id] && (
|
||||
<span
|
||||
onClick={() => setExpandedTestResult(expandedTestResult === model.id ? null : model.id)}
|
||||
style={{
|
||||
fontSize: '11px', padding: '4px 8px', cursor: 'pointer',
|
||||
background: testResults[model.id].success ? 'rgba(34,197,94,0.15)' : 'rgba(239,68,68,0.15)',
|
||||
color: testResults[model.id].success ? '#22C55E' : '#EF4444',
|
||||
borderRadius: '4px', border: `1px solid ${testResults[model.id].success ? 'rgba(34,197,94,0.3)' : 'rgba(239,68,68,0.3)'}`,
|
||||
whiteSpace: 'nowrap',
|
||||
}}>
|
||||
{testResults[model.id].success ? `✓ ${testResults[model.id].latency_ms || 0}ms` : '✗ 失败'}
|
||||
</span>
|
||||
)}
|
||||
{!model.isDefault && model.isEnabled && (
|
||||
<button onClick={() => handleSetDefault(model.id)} style={btnStyle('#5B5FED', 'small')}>设为默认</button>
|
||||
)}
|
||||
{expandedTestResult === model.id && testResults[model.id] && (
|
||||
<div style={{
|
||||
position: 'fixed', top: 0, left: 0, right: 0, bottom: 0,
|
||||
background: 'rgba(0,0,0,0.6)', zIndex: 1000,
|
||||
display: 'flex', alignItems: 'center', justifyContent: 'center',
|
||||
}} onClick={() => setExpandedTestResult(null)}>
|
||||
<div style={{
|
||||
background: '#111627', borderRadius: 12, padding: 24,
|
||||
maxWidth: 480, width: '90%',
|
||||
border: `1px solid ${testResults[model.id].success ? 'rgba(34,197,94,0.3)' : 'rgba(239,68,68,0.3)'}`,
|
||||
}} onClick={e => e.stopPropagation()}>
|
||||
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 16 }}>
|
||||
<h3 style={{ margin: 0, fontSize: 16, color: '#E2E8F0' }}>连接测试详情</h3>
|
||||
<button onClick={() => setExpandedTestResult(null)} style={{ background: 'none', border: 'none', color: '#6B7280', cursor: 'pointer', fontSize: 18 }}>×</button>
|
||||
</div>
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<span style={{ fontSize: 12, color: '#6B7280' }}>模型</span>
|
||||
<span style={{ fontSize: 14, color: '#E2E8F0', marginLeft: 8 }}>{model.name}</span>
|
||||
</div>
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<span style={{ fontSize: 12, color: '#6B7280' }}>类型</span>
|
||||
<span style={{ fontSize: 14, color: modelTypeLabels[getModelType(model.capabilities)].color, marginLeft: 8 }}>
|
||||
{modelTypeLabels[getModelType(model.capabilities)].label}
|
||||
</span>
|
||||
</div>
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<span style={{ fontSize: 12, color: '#6B7280' }}>状态</span>
|
||||
<span style={{
|
||||
fontSize: 14, marginLeft: 8, fontWeight: 600,
|
||||
color: testResults[model.id].success ? '#22C55E' : '#EF4444',
|
||||
}}>
|
||||
{testResults[model.id].success ? '✓ 成功' : '✗ 失败'}
|
||||
</span>
|
||||
</div>
|
||||
{testResults[model.id].latency_ms !== undefined && testResults[model.id].latency_ms !== null && (
|
||||
<div style={{ marginBottom: 12 }}>
|
||||
<span style={{ fontSize: 12, color: '#6B7280' }}>延迟</span>
|
||||
<span style={{ fontSize: 14, color: '#E2E8F0', marginLeft: 8, fontFamily: 'monospace' }}>
|
||||
{testResults[model.id].latency_ms}ms
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<div style={{ marginBottom: 16 }}>
|
||||
<span style={{ fontSize: 12, color: '#6B7280', display: 'block', marginBottom: 4 }}>详情</span>
|
||||
<div style={{
|
||||
fontSize: 13, color: '#9CA3AF', lineHeight: 1.6,
|
||||
background: 'rgba(0,0,0,0.3)', padding: 12, borderRadius: 8,
|
||||
wordBreak: 'break-word',
|
||||
}}>
|
||||
{testResults[model.id].message}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<button onClick={() => openModelEditor(model as AIModel)} style={btnStyle('#6B7280', 'small')}>编辑</button>
|
||||
<button onClick={() => handleDeleteModel(model.id)} style={btnStyle('#EF4444', 'small')}>删除</button>
|
||||
</div>
|
||||
|
||||
Reference in new issue
Block a user