Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 20ef1abe7d | |||
| 54290019c3 |
@@ -120,6 +120,8 @@ async def get_providers(db: Session = Depends(get_db)):
|
||||
"default_model": p.default_model,
|
||||
"supports_thinking": p.supports_thinking,
|
||||
"thinking_model": p.thinking_model,
|
||||
"supports_vision": p.supports_vision,
|
||||
"vision_model": p.vision_model,
|
||||
"max_tokens": p.max_tokens,
|
||||
"temperature": p.temperature,
|
||||
"is_active": p.is_active,
|
||||
|
||||
@@ -32,6 +32,10 @@ class LLMProvider(Base):
|
||||
supports_thinking = Column(Boolean, default=False) # 是否原生支持思考
|
||||
thinking_model = Column(String(100), nullable=True) # 思考模式模型名(如有单独模型)
|
||||
|
||||
# 视觉能力支持
|
||||
supports_vision = Column(Boolean, default=False) # 是否支持图片理解(多模态)
|
||||
vision_model = Column(String(100), nullable=True) # 视觉模型名(如与默认模型不同)
|
||||
|
||||
# 配额和限制
|
||||
max_tokens = Column(Integer, default=4096)
|
||||
temperature = Column(Float, default=0.7)
|
||||
|
||||
@@ -58,8 +58,8 @@
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<table class="table">
|
||||
<thead><tr><th>名称</th><th>API地址</th><th>默认模型</th><th>思考支持</th><th>状态</th><th>操作</th></tr></thead>
|
||||
<tbody id="providers-list"><tr><td colspan="6" class="text-center">加载中...</td></tr></tbody>
|
||||
<thead><tr><th>名称</th><th>API地址</th><th>默认模型</th><th>思考</th><th>视觉</th><th>状态</th><th>操作</th></tr></thead>
|
||||
<tbody id="providers-list"><tr><td colspan="7" class="text-center">加载中...</td></tr></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
@@ -162,6 +162,8 @@
|
||||
<div class="mt-3 form-check"><input type="checkbox" class="form-check-input" id="provider-active" checked><label class="form-check-label">启用</label></div>
|
||||
<hr><h6>思考功能</h6>
|
||||
<div class="thinking-config"><div class="row"><div class="col-md-6 form-check"><input type="checkbox" class="form-check-input" id="provider-supports-thinking"><label class="form-check-label">支持原生思考</label></div><div class="col-md-6"><label class="form-label">思考模型名</label><input type="text" class="form-control" id="provider-thinking-model"></div></div></div>
|
||||
<hr><h6>视觉能力</h6>
|
||||
<div class="thinking-config"><div class="row"><div class="col-md-6 form-check"><input type="checkbox" class="form-check-input" id="provider-supports-vision"><label class="form-check-label">支持图片理解</label></div><div class="col-md-6"><label class="form-label">视觉模型名</label><input type="text" class="form-control" id="provider-vision-model" placeholder="留空则使用默认模型"></div></div><small class="text-muted mt-2 d-block">启用后可上传图片让AI识别分析内容</small></div>
|
||||
<div class="mt-3"><button type="button" class="btn btn-outline-primary" onclick="fetchProviderModels()"><i class="ri-refresh-line"></i> 获取模型</button><button type="button" class="btn btn-outline-secondary" onclick="testProviderConnection()"><i class="ri-link"></i> 测试连接</button></div>
|
||||
<div class="mt-2" id="provider-models-preview"></div><div class="mt-2" id="provider-test-result"></div>
|
||||
</form></div>
|
||||
@@ -274,6 +276,7 @@
|
||||
tbody.innerHTML = providersData.map(p => `<tr>
|
||||
<td><strong>${p.name}</strong></td><td><small>${p.api_base||'-'}</small></td><td>${p.default_model||'auto'}</td>
|
||||
<td>${p.supports_thinking?'<span class="badge bg-success">支持</span>':'<span class="badge bg-secondary">不支持</span>'}</td>
|
||||
<td>${p.supports_vision?'<span class="badge bg-info">支持</span>':'<span class="badge bg-secondary">不支持</span>'}</td>
|
||||
<td>${p.is_active?'<span class="badge bg-success">启用</span>':'<span class="badge bg-secondary">禁用</span>'}</td>
|
||||
<td><button class="btn btn-sm btn-outline-primary" onclick="editProvider(${p.id})"><i class="ri-edit-line"></i></button>
|
||||
<button class="btn btn-sm btn-outline-danger" onclick="deleteProvider(${p.id},'${p.name}')"><i class="ri-delete-bin-line"></i></button></td>
|
||||
@@ -290,6 +293,8 @@
|
||||
document.getElementById('provider-form').reset();
|
||||
document.getElementById('provider-id').value = '';
|
||||
document.getElementById('provider-active').checked = true;
|
||||
document.getElementById('provider-supports-thinking').checked = false;
|
||||
document.getElementById('provider-supports-vision').checked = false;
|
||||
document.getElementById('provider-models-preview').innerHTML = '';
|
||||
document.getElementById('provider-test-result').innerHTML = '';
|
||||
new bootstrap.Modal(document.getElementById('providerModal')).show();
|
||||
@@ -310,6 +315,8 @@
|
||||
document.getElementById('provider-active').checked = p.is_active;
|
||||
document.getElementById('provider-supports-thinking').checked = p.supports_thinking;
|
||||
document.getElementById('provider-thinking-model').value = p.thinking_model || '';
|
||||
document.getElementById('provider-supports-vision').checked = p.supports_vision;
|
||||
document.getElementById('provider-vision-model').value = p.vision_model || '';
|
||||
new bootstrap.Modal(document.getElementById('providerModal')).show();
|
||||
}
|
||||
|
||||
@@ -326,7 +333,9 @@
|
||||
description: document.getElementById('provider-description').value,
|
||||
is_active: document.getElementById('provider-active').checked,
|
||||
supports_thinking: document.getElementById('provider-supports-thinking').checked,
|
||||
thinking_model: document.getElementById('provider-thinking-model').value
|
||||
thinking_model: document.getElementById('provider-thinking-model').value,
|
||||
supports_vision: document.getElementById('provider-supports-vision').checked,
|
||||
vision_model: document.getElementById('provider-vision-model').value
|
||||
};
|
||||
const res = await fetch(id ? `/api/v2/providers/${id}` : '/api/v2/providers', { method: id ? 'PUT' : 'POST', headers: {'Content-Type':'application/json'}, body: JSON.stringify(data) });
|
||||
const result = await res.json();
|
||||
|
||||
@@ -260,6 +260,7 @@
|
||||
let currentConversationId = null;
|
||||
let currentAgentId = null;
|
||||
let agents = [];
|
||||
let providers = []; // 大模型池数据
|
||||
let quickPhrases = [];
|
||||
let lastSentMessage = null; // 记录最后发送的消息
|
||||
let lastSentFiles = null; // 记录发送的文件
|
||||
@@ -272,6 +273,7 @@
|
||||
let messageVersions = {}; // 存储每个assistant消息的多个版本 { messageId: [{content, thinking}] }
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
loadProviders(); // 加载大模型池
|
||||
loadAgents();
|
||||
loadQuickPhrases();
|
||||
connectWebSocket();
|
||||
@@ -279,6 +281,23 @@
|
||||
setupTextarea();
|
||||
});
|
||||
|
||||
// 加载大模型池
|
||||
async function loadProviders() {
|
||||
try {
|
||||
const res = await fetch('/api/v2/providers');
|
||||
const data = await res.json();
|
||||
providers = data.providers || [];
|
||||
} catch (e) { console.error('加载Provider失败:', e); }
|
||||
}
|
||||
|
||||
// 检查当前Agent是否支持视觉
|
||||
function checkAgentVisionSupport() {
|
||||
const agent = agents.find(a => a.id === currentAgentId);
|
||||
if (!agent) return false;
|
||||
const provider = providers.find(p => p.id === agent.llm_provider_id);
|
||||
return provider?.supports_vision || false;
|
||||
}
|
||||
|
||||
// 加载Agent
|
||||
async function loadAgents() {
|
||||
try {
|
||||
@@ -868,6 +887,16 @@
|
||||
// 如果没有消息且没有文件,不发送
|
||||
if (!msg && pendingFiles.length === 0) return;
|
||||
|
||||
// 检查是否有图片上传
|
||||
const hasImages = pendingFiles.some(f => f.type && f.type.startsWith('image/'));
|
||||
if (hasImages && !checkAgentVisionSupport()) {
|
||||
const agent = agents.find(a => a.id === currentAgentId);
|
||||
const agentName = agent?.display_name || agent?.name || '当前Agent';
|
||||
alert(`⚠️ ${agentName} 不支持图片识别功能\n\n请选择支持视觉能力的Agent(如 vlm-agent),或者移除图片后再发送。`);
|
||||
document.getElementById('sendBtn').disabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
document.getElementById('sendBtn').disabled = true;
|
||||
input.value = '';
|
||||
input.style.height = 'auto';
|
||||
@@ -1014,6 +1043,21 @@
|
||||
}
|
||||
|
||||
previewArea.appendChild(previewItem);
|
||||
|
||||
// 如果上传的是图片且当前Agent不支持视觉,显示警告提示
|
||||
if (file.type.startsWith('image/') && !checkAgentVisionSupport()) {
|
||||
const agent = agents.find(a => a.id === currentAgentId);
|
||||
const agentName = agent?.display_name || agent?.name || '当前Agent';
|
||||
const warningDiv = document.createElement('div');
|
||||
warningDiv.className = 'vision-warning';
|
||||
warningDiv.id = 'vision-warning-tip';
|
||||
warningDiv.style.cssText = 'margin-top:8px;padding:8px 12px;background:#fff3cd;border:1px solid #ffc107;border-radius:6px;font-size:13px;color:#856404;';
|
||||
warningDiv.innerHTML = `<i class="ri-alert-line"></i> <strong>${agentName}</strong> 不支持图片识别,请选择支持视觉的Agent或移除图片`;
|
||||
// 避免重复添加
|
||||
if (!document.getElementById('vision-warning-tip')) {
|
||||
previewArea.appendChild(warningDiv);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 根据文件类型选择读取方式
|
||||
@@ -1035,6 +1079,13 @@
|
||||
pendingFiles = pendingFiles.filter(f => f.id !== fileId);
|
||||
const preview = document.getElementById(fileId + '-preview');
|
||||
if (preview) preview.remove();
|
||||
|
||||
// 如果没有图片了,移除视觉警告提示
|
||||
const hasImages = pendingFiles.some(f => f.type && f.type.startsWith('image/'));
|
||||
if (!hasImages) {
|
||||
const warning = document.getElementById('vision-warning-tip');
|
||||
if (warning) warning.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// 发送消息
|
||||
|
||||
BIN
uploads/images/1776134549_0c993820.jpeg
Normal file
BIN
uploads/images/1776134549_0c993820.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 225 KiB |
BIN
uploads/images/1776134674_10f77dae.png
Normal file
BIN
uploads/images/1776134674_10f77dae.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 147 KiB |
Reference in New Issue
Block a user