feat: 大模型配置增加思考模式和视觉能力选项
- 数据库新增 enable_thinking 和 enable_vision 字段 - 后台管理表格显示思考🧠和视觉👁️能力状态 - 添加/编辑表单增加开关按钮选择 - 兼容旧数据库自动添加新字段
This commit is contained in:
@@ -54,6 +54,8 @@ def init_db():
|
|||||||
model TEXT NOT NULL,
|
model TEXT NOT NULL,
|
||||||
max_tokens INTEGER DEFAULT 2048,
|
max_tokens INTEGER DEFAULT 2048,
|
||||||
temperature REAL DEFAULT 0.7,
|
temperature REAL DEFAULT 0.7,
|
||||||
|
enable_thinking INTEGER DEFAULT 0,
|
||||||
|
enable_vision INTEGER DEFAULT 0,
|
||||||
is_default INTEGER DEFAULT 0,
|
is_default INTEGER DEFAULT 0,
|
||||||
is_active INTEGER DEFAULT 1,
|
is_active INTEGER DEFAULT 1,
|
||||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
@@ -221,6 +223,14 @@ def init_db():
|
|||||||
'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', 10, 1)
|
'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', 10, 1)
|
||||||
''')
|
''')
|
||||||
|
|
||||||
|
# 检测并添加 llm_configs 新字段(兼容旧数据库)
|
||||||
|
cursor.execute("PRAGMA table_info(llm_configs)")
|
||||||
|
llm_columns = [col[1] for col in cursor.fetchall()]
|
||||||
|
if 'enable_thinking' not in llm_columns:
|
||||||
|
cursor.execute("ALTER TABLE llm_configs ADD COLUMN enable_thinking INTEGER DEFAULT 0")
|
||||||
|
if 'enable_vision' not in llm_columns:
|
||||||
|
cursor.execute("ALTER TABLE llm_configs ADD COLUMN enable_vision INTEGER DEFAULT 0")
|
||||||
|
|
||||||
# 初始化默认对话配置
|
# 初始化默认对话配置
|
||||||
cursor.execute('SELECT COUNT(*) FROM chat_configs')
|
cursor.execute('SELECT COUNT(*) FROM chat_configs')
|
||||||
if cursor.fetchone()[0] == 0:
|
if cursor.fetchone()[0] == 0:
|
||||||
@@ -1005,10 +1015,11 @@ def add_llm_config():
|
|||||||
conn = get_db()
|
conn = get_db()
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
cursor.execute('''
|
cursor.execute('''
|
||||||
INSERT INTO llm_configs (name, provider, api_url, api_key, model, max_tokens, temperature)
|
INSERT INTO llm_configs (name, provider, api_url, api_key, model, max_tokens, temperature, enable_thinking, enable_vision)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||||
''', (data['name'], data['provider'], data['api_url'], data['api_key'],
|
''', (data['name'], data['provider'], data['api_url'], data['api_key'],
|
||||||
data['model'], data.get('max_tokens', 2048), data.get('temperature', 0.7)))
|
data['model'], data.get('max_tokens', 2048), data.get('temperature', 0.7),
|
||||||
|
data.get('enable_thinking', 0), data.get('enable_vision', 0)))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
config_id = cursor.lastrowid
|
config_id = cursor.lastrowid
|
||||||
conn.close()
|
conn.close()
|
||||||
@@ -1023,9 +1034,10 @@ def update_llm_config(id):
|
|||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
cursor.execute('''
|
cursor.execute('''
|
||||||
UPDATE llm_configs SET name=?, provider=?, api_url=?, api_key=?, model=?,
|
UPDATE llm_configs SET name=?, provider=?, api_url=?, api_key=?, model=?,
|
||||||
max_tokens=?, temperature=?, updated_at=CURRENT_TIMESTAMP WHERE id=?
|
max_tokens=?, temperature=?, enable_thinking=?, enable_vision=?, updated_at=CURRENT_TIMESTAMP WHERE id=?
|
||||||
''', (data['name'], data['provider'], data['api_url'], data['api_key'],
|
''', (data['name'], data['provider'], data['api_url'], data['api_key'],
|
||||||
data['model'], data.get('max_tokens', 2048), data.get('temperature', 0.7), id))
|
data['model'], data.get('max_tokens', 2048), data.get('temperature', 0.7),
|
||||||
|
data.get('enable_thinking', 0), data.get('enable_vision', 0), id))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
return jsonify({'success': True})
|
return jsonify({'success': True})
|
||||||
|
|||||||
@@ -401,6 +401,54 @@
|
|||||||
.toast.show {
|
.toast.show {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 表单行(双列布局) */
|
||||||
|
.form-row {
|
||||||
|
display: flex;
|
||||||
|
gap: 20px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group-half {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 表单提示 */
|
||||||
|
.form-tip {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #999;
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 开关按钮 */
|
||||||
|
.toggle-switch {
|
||||||
|
width: 50px;
|
||||||
|
height: 26px;
|
||||||
|
background: #ccc;
|
||||||
|
border-radius: 13px;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
transition: background 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-switch.active {
|
||||||
|
background: var(--primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-slider {
|
||||||
|
width: 22px;
|
||||||
|
height: 22px;
|
||||||
|
background: white;
|
||||||
|
border-radius: 50%;
|
||||||
|
position: absolute;
|
||||||
|
top: 2px;
|
||||||
|
left: 2px;
|
||||||
|
transition: left 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.toggle-switch.active .toggle-slider {
|
||||||
|
left: 26px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
66
www/admin.js
66
www/admin.js
@@ -559,7 +559,8 @@ async function loadLLMPage(content) {
|
|||||||
<th>名称</th>
|
<th>名称</th>
|
||||||
<th>提供商</th>
|
<th>提供商</th>
|
||||||
<th>模型</th>
|
<th>模型</th>
|
||||||
<th>API URL</th>
|
<th>思考</th>
|
||||||
|
<th>视觉</th>
|
||||||
<th>状态</th>
|
<th>状态</th>
|
||||||
<th>操作</th>
|
<th>操作</th>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -570,7 +571,8 @@ async function loadLLMPage(content) {
|
|||||||
<td>${c.name} ${c.is_default ? '<span class="default-badge">默认</span>' : ''}</td>
|
<td>${c.name} ${c.is_default ? '<span class="default-badge">默认</span>' : ''}</td>
|
||||||
<td>${c.provider}</td>
|
<td>${c.provider}</td>
|
||||||
<td>${c.model}</td>
|
<td>${c.model}</td>
|
||||||
<td style="max-width: 200px; overflow: hidden; text-overflow: ellipsis;">${c.api_url}</td>
|
<td>${c.enable_thinking ? '🧠 支持' : '—'}</td>
|
||||||
|
<td>${c.enable_vision ? '👁️ 支持' : '—'}</td>
|
||||||
<td>${c.is_active ? '✅ 启用' : '❌ 禁用'}</td>
|
<td>${c.is_active ? '✅ 启用' : '❌ 禁用'}</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="action-btns">
|
<div class="action-btns">
|
||||||
@@ -622,8 +624,28 @@ function showAddLLMModal() {
|
|||||||
<label class="form-label">Temperature</label>
|
<label class="form-label">Temperature</label>
|
||||||
<input type="number" class="form-input" id="llmTemperature" value="0.7" step="0.1">
|
<input type="number" class="form-input" id="llmTemperature" value="0.7" step="0.1">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group form-group-half">
|
||||||
|
<label class="form-label">思考模式 🧠</label>
|
||||||
|
<div class="toggle-switch" id="llmThinkingToggle" data-value="0">
|
||||||
|
<div class="toggle-slider"></div>
|
||||||
|
</div>
|
||||||
|
<div class="form-tip">支持深度思考/推理能力</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group form-group-half">
|
||||||
|
<label class="form-label">视觉能力 👁️</label>
|
||||||
|
<div class="toggle-switch" id="llmVisionToggle" data-value="0">
|
||||||
|
<div class="toggle-slider"></div>
|
||||||
|
</div>
|
||||||
|
<div class="form-tip">支持图片输入/多模态</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<button class="form-submit" onclick="saveLLM()">保存</button>
|
<button class="form-submit" onclick="saveLLM()">保存</button>
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
// 绑定开关事件
|
||||||
|
bindToggleSwitch('llmThinkingToggle');
|
||||||
|
bindToggleSwitch('llmVisionToggle');
|
||||||
}
|
}
|
||||||
|
|
||||||
function showEditLLMModal(id) {
|
function showEditLLMModal(id) {
|
||||||
@@ -664,8 +686,28 @@ function showEditLLMModal(id) {
|
|||||||
<label class="form-label">Temperature</label>
|
<label class="form-label">Temperature</label>
|
||||||
<input type="number" class="form-input" id="llmTemperature" value="${config.temperature}" step="0.1">
|
<input type="number" class="form-input" id="llmTemperature" value="${config.temperature}" step="0.1">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group form-group-half">
|
||||||
|
<label class="form-label">思考模式 🧠</label>
|
||||||
|
<div class="toggle-switch ${config.enable_thinking ? 'active' : ''}" id="llmThinkingToggle" data-value="${config.enable_thinking || 0}">
|
||||||
|
<div class="toggle-slider"></div>
|
||||||
|
</div>
|
||||||
|
<div class="form-tip">支持深度思考/推理能力</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group form-group-half">
|
||||||
|
<label class="form-label">视觉能力 👁️</label>
|
||||||
|
<div class="toggle-switch ${config.enable_vision ? 'active' : ''}" id="llmVisionToggle" data-value="${config.enable_vision || 0}">
|
||||||
|
<div class="toggle-slider"></div>
|
||||||
|
</div>
|
||||||
|
<div class="form-tip">支持图片输入/多模态</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<button class="form-submit" onclick="updateLLM(${id})">保存</button>
|
<button class="form-submit" onclick="updateLLM(${id})">保存</button>
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
// 绑定开关事件
|
||||||
|
bindToggleSwitch('llmThinkingToggle');
|
||||||
|
bindToggleSwitch('llmVisionToggle');
|
||||||
}
|
}
|
||||||
|
|
||||||
async function saveLLM() {
|
async function saveLLM() {
|
||||||
@@ -676,7 +718,9 @@ async function saveLLM() {
|
|||||||
api_key: document.getElementById('llmApiKey').value,
|
api_key: document.getElementById('llmApiKey').value,
|
||||||
model: document.getElementById('llmModel').value,
|
model: document.getElementById('llmModel').value,
|
||||||
max_tokens: parseInt(document.getElementById('llmMaxTokens').value),
|
max_tokens: parseInt(document.getElementById('llmMaxTokens').value),
|
||||||
temperature: parseFloat(document.getElementById('llmTemperature').value)
|
temperature: parseFloat(document.getElementById('llmTemperature').value),
|
||||||
|
enable_thinking: parseInt(document.getElementById('llmThinkingToggle')?.dataset.value || 0),
|
||||||
|
enable_vision: parseInt(document.getElementById('llmVisionToggle')?.dataset.value || 0)
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!data.name || !data.api_url || !data.api_key || !data.model) {
|
if (!data.name || !data.api_url || !data.api_key || !data.model) {
|
||||||
@@ -698,7 +742,9 @@ async function updateLLM(id) {
|
|||||||
api_key: document.getElementById('llmApiKey').value,
|
api_key: document.getElementById('llmApiKey').value,
|
||||||
model: document.getElementById('llmModel').value,
|
model: document.getElementById('llmModel').value,
|
||||||
max_tokens: parseInt(document.getElementById('llmMaxTokens').value),
|
max_tokens: parseInt(document.getElementById('llmMaxTokens').value),
|
||||||
temperature: parseFloat(document.getElementById('llmTemperature').value)
|
temperature: parseFloat(document.getElementById('llmTemperature').value),
|
||||||
|
enable_thinking: parseInt(document.getElementById('llmThinkingToggle')?.dataset.value || 0),
|
||||||
|
enable_vision: parseInt(document.getElementById('llmVisionToggle')?.dataset.value || 0)
|
||||||
};
|
};
|
||||||
|
|
||||||
await fetchAPI(`/api/admin/llm/${id}`, 'PUT', data);
|
await fetchAPI(`/api/admin/llm/${id}`, 'PUT', data);
|
||||||
@@ -1512,6 +1558,18 @@ function closeModal() {
|
|||||||
document.getElementById('modal').classList.remove('show');
|
document.getElementById('modal').classList.remove('show');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function bindToggleSwitch(id) {
|
||||||
|
const toggle = document.getElementById(id);
|
||||||
|
if (!toggle) return;
|
||||||
|
|
||||||
|
toggle.addEventListener('click', () => {
|
||||||
|
const current = parseInt(toggle.dataset.value) || 0;
|
||||||
|
const newValue = current === 0 ? 1 : 0;
|
||||||
|
toggle.dataset.value = newValue;
|
||||||
|
toggle.classList.toggle('active', newValue === 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function showToast(message) {
|
function showToast(message) {
|
||||||
const toast = document.getElementById('toast');
|
const toast = document.getElementById('toast');
|
||||||
toast.textContent = message;
|
toast.textContent = message;
|
||||||
|
|||||||
Reference in New Issue
Block a user