feat: 模型库能力默认模型UI + 前台Auto默认模型 + AGNES直连修复 + 意图识别增强

This commit is contained in:
QihuAI Design committed 2026-07-14 05:47:57 +08:00
1 parent 2847289921
commit 945eeb8ba0
4 files changed
+121 -23

No files matched your search

+6
View File
@@ -616,6 +616,12 @@ export const adminAPI = {
setDefaultModel: (id: string) => put<any>(`/admin/models/${id}/set-default`),
// 按能力默认模型(Auto 自动模式使用)
getCapabilities: () => get<any[]>('/admin/models/capabilities'),
getCapabilityDefaults: () => get<any>('/admin/models/capability-defaults'),
setCapabilityDefault: (capability: string, modelId: string | null) =>
put<any>('/admin/models/capability-defaults', { capability, model_id: modelId }),
// Phase 2: Enhanced model management
testModelConnection: (id: string) => post<any>(`/admin/models/${id}/test-connection`),
+84 -15
View File
@@ -15,23 +15,15 @@ const formatNumber = (n: number) => {
};
const capabilityColors: Record<string, string> = {
text: '#5B5FED',
vision: '#219B5E',
audio: '#E8922C',
video: '#D9383A',
image: '#D9383A',
thinking: '#8B5CF6',
tool: '#3B82F6',
text: '#5B5FED', thinking: '#8B5CF6', vision: '#219B5E', tool: '#3B82F6',
image: '#D9383A', audio: '#E8922C', asr: '#0EA5E9', ocr: '#F59E0B',
embedding: '#10B981', rerank: '#EC4899', video: '#EF4444',
};
const capabilityLabels: Record<string, string> = {
text: '文本',
vision: '视觉',
audio: '音频',
video: '视频',
image: '图像',
thinking: '思考',
tool: '工具',
text: '文本对话', thinking: '推理思考', vision: '视觉理解', tool: '工具调用',
image: '图像生成', audio: '语音合成', asr: '语音识别', ocr: 'OCR识别',
embedding: '嵌入向量', rerank: '重排序', video: '视频生成',
};
// Test result type
@@ -69,6 +61,12 @@ const Models: React.FC = () => {
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
// 能力默认模型(Auto 自动模式)
const [capabilities, setCapabilities] = useState<any[]>([]);
const [capabilityDefaults, setCapabilityDefaults] = useState<Record<string, string>>({});
const [capabilityModels, setCapabilityModels] = useState<any[]>([]);
const [savingCaps, setSavingCaps] = useState<Set<string>>(new Set());
// Model test state
const [testingModelId, setTestingModelId] = useState<string | null>(null);
const [testResult, setTestResult] = useState<TestResult | null>(null);
@@ -85,10 +83,11 @@ const Models: React.FC = () => {
const loadData = useCallback(async () => {
try {
setLoading(true);
const [provRes, modelsRes, protoRes] = await Promise.all([
const [provRes, modelsRes, protoRes, capRes] = await Promise.all([
adminAPI.getProviders(),
adminAPI.getAdminModels({ page_size: 100 }),
adminAPI.getProtocols(),
adminAPI.getCapabilityDefaults(),
]);
setProviders(Array.isArray(provRes) ? provRes : (provRes.data || []));
setProtocols(Array.isArray(protoRes) ? protoRes : (protoRes.data || []));
@@ -105,6 +104,11 @@ const Models: React.FC = () => {
totalTokens: m.totalTokens ?? m.total_tokens ?? 0,
}));
setModels(mappedModels);
// 能力默认模型
const cd = (capRes.data || capRes) as any;
setCapabilities(cd.capabilities || []);
setCapabilityDefaults(cd.defaults || {});
setCapabilityModels(cd.models || []);
} catch (err: any) {
setError('加载数据失败:' + (err.message || err.detail || JSON.stringify(err)));
} finally {
@@ -278,6 +282,23 @@ const Models: React.FC = () => {
}
};
// 设置/清除某个能力的默认模型(Auto 自动模式使用)
const handleSetCapabilityDefault = async (capId: string, modelId: string | null) => {
try {
setSavingCaps(prev => new Set(prev).add(capId));
const res = await adminAPI.setCapabilityDefault(capId, modelId) as any;
setCapabilityDefaults((res && res.defaults) || { ...capabilityDefaults, [capId]: modelId || undefined } as Record<string, string>);
} catch (err: any) {
setError(err.message || '设置失败');
} finally {
setSavingCaps(prev => {
const n = new Set(prev);
n.delete(capId);
return n;
});
}
};
// Toggle capability
const toggleCapability = (cap: string) => {
if (!editingModel) return;
@@ -476,6 +497,54 @@ const Models: React.FC = () => {
</div>
)}
{/* ===== 能力默认模型(Auto 自动模式) ===== */}
{capabilities.length > 0 && (
<div style={{ background: '#fff', borderRadius: '8px', border: '1px solid #E5E7EB', padding: '20px', marginTop: '16px' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: '4px', flexWrap: 'wrap', gap: '8px' }}>
<h3 style={{ margin: 0, fontSize: '16px', fontWeight: 600 }}>能力默认模型</h3>
<span style={{ fontSize: '12px', color: '#888' }}>
为每项能力指定默认模型,前台「Auto 自动」将按对话意图自动匹配
</span>
</div>
<p style={{ margin: '0 0 16px', fontSize: '13px', color: '#999' }}>
选择「Auto 自动」时,系统依据用户输入(如含「画图 / 视频」)自动选用对应能力的默认模型;文本类能力在未单独配置时回退到全局默认模型。
</p>
<div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(300px, 1fr))', gap: '12px' }}>
{capabilities.map((cap: any) => {
const capModels = capabilityModels.filter(
(m: any) => (m.capabilities || []).includes(cap.id) || capabilityDefaults[cap.id] === m.id
);
const cur = capabilityDefaults[cap.id] || '';
const saving = savingCaps.has(cap.id);
const dotColor = cap.color || capabilityColors[cap.id] || '#6B7280';
return (
<div key={cap.id} style={{ border: '1px solid #EEF0F4', borderRadius: '8px', padding: '12px 14px', background: '#FBFCFE' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '6px' }}>
<span style={{ width: '10px', height: '10px', borderRadius: '3px', background: dotColor, flexShrink: 0 }} />
<span style={{ fontWeight: 600, fontSize: '14px' }}>{cap.label}</span>
<span style={{ fontSize: '11px', color: '#bbb', marginLeft: 'auto' }}>{cap.id}</span>
</div>
<div style={{ fontSize: '12px', color: '#8a8a8a', marginBottom: '10px', minHeight: '32px' }}>{cap.desc}</div>
<select
value={cur}
disabled={saving}
onChange={e => handleSetCapabilityDefault(cap.id, e.target.value || null)}
style={{ width: '100%', padding: '7px 10px', border: '1px solid #D1D5DB', borderRadius: '6px', fontSize: '13px', boxSizing: 'border-box', background: '#fff', color: '#333' }}
>
<option value="">— 未设置(回退全局默认)—</option>
{capModels.map((m: any) => (
<option key={m.id} value={m.id}>
{m.display_name || m.name}{m.provider_name ? `(${m.provider_name})` : ''}
</option>
))}
</select>
</div>
);
})}
</div>
</div>
)}
{/* Provider Modal */}
{showAddProvider && (
<div style={modalOverlay}>
+20 -5
View File
@@ -307,7 +307,7 @@ const ChatPage: React.FC = () => {
const { id } = useParams<{ id?: string }>();
const navigate = useNavigate();
const [searchQuery, setSearchQuery] = useState('');
const [selectedModelId, setSelectedModelId] = useState('');
const [selectedModelId, setSelectedModelId] = useState('auto');
const [chatModels, setChatModels] = useState<any[]>([]);
const [inputValue, setInputValue] = useState('');
const [sending, setSending] = useState(false);
@@ -339,7 +339,7 @@ const ChatPage: React.FC = () => {
const models = Array.isArray(data) ? data : (data?.items || []);
setChatModels(models);
if (models.length > 0 && !selectedModelId) {
setSelectedModelId(models[0].id);
setSelectedModelId('auto');
}
}).catch(() => {
setChatModels([]);
@@ -463,8 +463,12 @@ const ChatPage: React.FC = () => {
return [...pinned.filter(c => c.title.toLowerCase().includes(q)), ...unpinned.filter(c => c.title.toLowerCase().includes(q))];
}, [conversations, searchQuery]);
// Selected model info
const selectedModel = chatModels.find(m => m.id === selectedModelId) || chatModels[0] || { id: '', name: '选择模型', status: 'free', provider_name: '' };
// Selected model info(Auto 自动:按对话意图自动匹配后台各能力默认模型)
const isAutoModel = selectedModelId === 'auto';
const AUTO_MODEL = { id: 'auto', name: 'Auto 自动', status: 'free', provider_name: '按能力智能匹配', capabilities: [], price_per_million_tokens: 0 };
const selectedModel = isAutoModel
? AUTO_MODEL
: (chatModels.find(m => m.id === selectedModelId) || chatModels[0] || AUTO_MODEL);
// Scroll to bottom when messages change
useEffect(() => {
@@ -481,7 +485,7 @@ const ChatPage: React.FC = () => {
// Handle new conversation
const handleNewConversation = async () => {
try {
const res = await chatAPI.createConversation({ title: '新对话', model_id: selectedModelId });
const res = await chatAPI.createConversation({ title: '新对话', model_id: selectedModelId === 'auto' ? (chatModels[0]?.id || '') : selectedModelId });
const newConv = unwrapData(res);
const conv: Conversation = {
id: newConv.id,
@@ -792,6 +796,17 @@ const ChatPage: React.FC = () => {
</div>
}
>
<button
key="auto"
className={`dropdown-item ${selectedModelId === 'auto' ? 'selected' : ''}`}
onClick={() => setSelectedModelId('auto')}
>
<div className="model-dropdown-item">
<span>🤖 Auto 自动</span>
<span className="model-provider-tag">按能力智能匹配</span>
<span className="model-cap-badge">智能</span>
</div>
</button>
{chatModels.filter(m => m.is_enabled !== false).map(model => (
<button
key={model.id}