feat: 模型库能力默认模型UI + 前台Auto默认模型 + AGNES直连修复 + 意图识别增强
This commit is contained in:
1 parent
2847289921
commit
945eeb8ba0
4 files changed
+121
-23
No files matched your search
@@ -46,9 +46,17 @@ def detect_media_intent(text: str) -> str | None:
|
|||||||
# 疑问句不触发媒体生成
|
# 疑问句不触发媒体生成
|
||||||
if t.endswith(("?", "?")) or any(q in t for q in ("吗", "呢", "怎么", "如何", "能否", "是否可以", "可以吗", "会不会", "why", "how")):
|
if t.endswith(("?", "?")) or any(q in t for q in ("吗", "呢", "怎么", "如何", "能否", "是否可以", "可以吗", "会不会", "why", "how")):
|
||||||
return None
|
return None
|
||||||
video_kw = ("视频", "短片", "影片", "vide", "动画视频", "一段视频", "做个视频", "生成视频")
|
video_kw = ("视频", "短片", "影片", "vide", "动画视频", "一段视频", "做个视频", "生成视频",
|
||||||
image_kw = ("图片", "图像", "插画", "海报", "照片", "imag", "画一张", "画个", "生成图片", "一张图", "一幅画", "生成一幅")
|
"视频生成", "来段视频", "拍个视频", "做段视频", "视频剪辑")
|
||||||
audio_kw = ("语音", "配音", "朗读", "播报", "念一段", "有声", "tts", "音频", "生成语音")
|
image_kw = ("图片", "图像", "插画", "海报", "照片", "头像", "壁纸", "漫画", "表情包", "logo", "图标",
|
||||||
|
"imag", "画一张", "画张", "画一只", "画只", "画个", "画幅",
|
||||||
|
"来张", "来一张", "来幅", "发张图", "发个图",
|
||||||
|
"生成图片", "生成一张", "生成一幅", "生成图", "图像生成",
|
||||||
|
"做一张", "做张图", "做张", "出张图", "出一张图",
|
||||||
|
"帮我画", "给我画", "请画", "绘制", "作画", "ai绘画", "ai画图",
|
||||||
|
"画个图", "画张图", "画一幅图", "一张图", "一幅画", "生成一幅")
|
||||||
|
audio_kw = ("语音", "配音", "朗读", "播报", "念一段", "有声", "tts", "音频", "生成语音",
|
||||||
|
"读一下", "念一下", "语音合成")
|
||||||
if any(k in t for k in video_kw):
|
if any(k in t for k in video_kw):
|
||||||
return "video"
|
return "video"
|
||||||
if any(k in t for k in image_kw):
|
if any(k in t for k in image_kw):
|
||||||
|
|||||||
@@ -616,6 +616,12 @@ export const adminAPI = {
|
|||||||
|
|
||||||
setDefaultModel: (id: string) => put<any>(`/admin/models/${id}/set-default`),
|
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
|
// Phase 2: Enhanced model management
|
||||||
|
|
||||||
testModelConnection: (id: string) => post<any>(`/admin/models/${id}/test-connection`),
|
testModelConnection: (id: string) => post<any>(`/admin/models/${id}/test-connection`),
|
||||||
|
|||||||
@@ -15,23 +15,15 @@ const formatNumber = (n: number) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const capabilityColors: Record<string, string> = {
|
const capabilityColors: Record<string, string> = {
|
||||||
text: '#5B5FED',
|
text: '#5B5FED', thinking: '#8B5CF6', vision: '#219B5E', tool: '#3B82F6',
|
||||||
vision: '#219B5E',
|
image: '#D9383A', audio: '#E8922C', asr: '#0EA5E9', ocr: '#F59E0B',
|
||||||
audio: '#E8922C',
|
embedding: '#10B981', rerank: '#EC4899', video: '#EF4444',
|
||||||
video: '#D9383A',
|
|
||||||
image: '#D9383A',
|
|
||||||
thinking: '#8B5CF6',
|
|
||||||
tool: '#3B82F6',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const capabilityLabels: Record<string, string> = {
|
const capabilityLabels: Record<string, string> = {
|
||||||
text: '文本',
|
text: '文本对话', thinking: '推理思考', vision: '视觉理解', tool: '工具调用',
|
||||||
vision: '视觉',
|
image: '图像生成', audio: '语音合成', asr: '语音识别', ocr: 'OCR识别',
|
||||||
audio: '音频',
|
embedding: '嵌入向量', rerank: '重排序', video: '视频生成',
|
||||||
video: '视频',
|
|
||||||
image: '图像',
|
|
||||||
thinking: '思考',
|
|
||||||
tool: '工具',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Test result type
|
// Test result type
|
||||||
@@ -69,6 +61,12 @@ const Models: React.FC = () => {
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
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
|
// Model test state
|
||||||
const [testingModelId, setTestingModelId] = useState<string | null>(null);
|
const [testingModelId, setTestingModelId] = useState<string | null>(null);
|
||||||
const [testResult, setTestResult] = useState<TestResult | null>(null);
|
const [testResult, setTestResult] = useState<TestResult | null>(null);
|
||||||
@@ -85,10 +83,11 @@ const Models: React.FC = () => {
|
|||||||
const loadData = useCallback(async () => {
|
const loadData = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
const [provRes, modelsRes, protoRes] = await Promise.all([
|
const [provRes, modelsRes, protoRes, capRes] = await Promise.all([
|
||||||
adminAPI.getProviders(),
|
adminAPI.getProviders(),
|
||||||
adminAPI.getAdminModels({ page_size: 100 }),
|
adminAPI.getAdminModels({ page_size: 100 }),
|
||||||
adminAPI.getProtocols(),
|
adminAPI.getProtocols(),
|
||||||
|
adminAPI.getCapabilityDefaults(),
|
||||||
]);
|
]);
|
||||||
setProviders(Array.isArray(provRes) ? provRes : (provRes.data || []));
|
setProviders(Array.isArray(provRes) ? provRes : (provRes.data || []));
|
||||||
setProtocols(Array.isArray(protoRes) ? protoRes : (protoRes.data || []));
|
setProtocols(Array.isArray(protoRes) ? protoRes : (protoRes.data || []));
|
||||||
@@ -105,6 +104,11 @@ const Models: React.FC = () => {
|
|||||||
totalTokens: m.totalTokens ?? m.total_tokens ?? 0,
|
totalTokens: m.totalTokens ?? m.total_tokens ?? 0,
|
||||||
}));
|
}));
|
||||||
setModels(mappedModels);
|
setModels(mappedModels);
|
||||||
|
// 能力默认模型
|
||||||
|
const cd = (capRes.data || capRes) as any;
|
||||||
|
setCapabilities(cd.capabilities || []);
|
||||||
|
setCapabilityDefaults(cd.defaults || {});
|
||||||
|
setCapabilityModels(cd.models || []);
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
setError('加载数据失败:' + (err.message || err.detail || JSON.stringify(err)));
|
setError('加载数据失败:' + (err.message || err.detail || JSON.stringify(err)));
|
||||||
} finally {
|
} 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
|
// Toggle capability
|
||||||
const toggleCapability = (cap: string) => {
|
const toggleCapability = (cap: string) => {
|
||||||
if (!editingModel) return;
|
if (!editingModel) return;
|
||||||
@@ -476,6 +497,54 @@ const Models: React.FC = () => {
|
|||||||
</div>
|
</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 */}
|
{/* Provider Modal */}
|
||||||
{showAddProvider && (
|
{showAddProvider && (
|
||||||
<div style={modalOverlay}>
|
<div style={modalOverlay}>
|
||||||
|
|||||||
@@ -307,7 +307,7 @@ const ChatPage: React.FC = () => {
|
|||||||
const { id } = useParams<{ id?: string }>();
|
const { id } = useParams<{ id?: string }>();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
const [selectedModelId, setSelectedModelId] = useState('');
|
const [selectedModelId, setSelectedModelId] = useState('auto');
|
||||||
const [chatModels, setChatModels] = useState<any[]>([]);
|
const [chatModels, setChatModels] = useState<any[]>([]);
|
||||||
const [inputValue, setInputValue] = useState('');
|
const [inputValue, setInputValue] = useState('');
|
||||||
const [sending, setSending] = useState(false);
|
const [sending, setSending] = useState(false);
|
||||||
@@ -339,7 +339,7 @@ const ChatPage: React.FC = () => {
|
|||||||
const models = Array.isArray(data) ? data : (data?.items || []);
|
const models = Array.isArray(data) ? data : (data?.items || []);
|
||||||
setChatModels(models);
|
setChatModels(models);
|
||||||
if (models.length > 0 && !selectedModelId) {
|
if (models.length > 0 && !selectedModelId) {
|
||||||
setSelectedModelId(models[0].id);
|
setSelectedModelId('auto');
|
||||||
}
|
}
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
setChatModels([]);
|
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))];
|
return [...pinned.filter(c => c.title.toLowerCase().includes(q)), ...unpinned.filter(c => c.title.toLowerCase().includes(q))];
|
||||||
}, [conversations, searchQuery]);
|
}, [conversations, searchQuery]);
|
||||||
|
|
||||||
// Selected model info
|
// Selected model info(Auto 自动:按对话意图自动匹配后台各能力默认模型)
|
||||||
const selectedModel = chatModels.find(m => m.id === selectedModelId) || chatModels[0] || { id: '', name: '选择模型', status: 'free', provider_name: '' };
|
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
|
// Scroll to bottom when messages change
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -481,7 +485,7 @@ const ChatPage: React.FC = () => {
|
|||||||
// Handle new conversation
|
// Handle new conversation
|
||||||
const handleNewConversation = async () => {
|
const handleNewConversation = async () => {
|
||||||
try {
|
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 newConv = unwrapData(res);
|
||||||
const conv: Conversation = {
|
const conv: Conversation = {
|
||||||
id: newConv.id,
|
id: newConv.id,
|
||||||
@@ -792,6 +796,17 @@ const ChatPage: React.FC = () => {
|
|||||||
</div>
|
</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 => (
|
{chatModels.filter(m => m.is_enabled !== false).map(model => (
|
||||||
<button
|
<button
|
||||||
key={model.id}
|
key={model.id}
|
||||||
|
|||||||
Reference in new issue
Block a user