Compare commits

..

27 Commits

Author SHA1 Message Date
c439842bb2 feat: 大模型配置添加思考模式和视觉能力属性
- 数据库 llm_configs 表新增 enable_thinking 和 enable_vision 字段
- 后台管理界面大模型列表显示思考模式/视觉能力状态
- 添加/编辑大模型配置支持设置这两个能力开关
- 前端配置API返回LLM能力信息
- 对话界面根据能力显示或隐藏深度思考按钮
- 对话界面根据能力显示或隐藏上传图片选项
- 不支持视觉能力时上传图片提示用户等待升级

enable_thinking: 控制深度思考功能可用性
enable_vision: 控制图片分析功能可用性
2026-04-29 11:37:57 +08:00
d4b1f3dfff fix: 自动拼接/chat/completions到API URL 2026-04-29 00:30:47 +08:00
dc2e822ea9 fix: 普通对话使用后台配置的LLM 2026-04-28 22:53:22 +08:00
53db607b8d feat: TTS文本清理,过滤Markdown特殊字符和表情 2026-04-28 17:59:15 +08:00
c346418d68 feat: TTS流式播放+新对话默认关闭 2026-04-28 17:51:47 +08:00
7de13ffc6d feat: AI回复语音播放功能(Edge TTS) 2026-04-28 17:28:07 +08:00
60db170c0d fix: 添加获取用户信息的GET API路由 2026-04-28 13:09:13 +08:00
336e3cd12f fix: 登录用户换设备后头像同步,初始化从后台获取最新用户信息 2026-04-28 13:05:54 +08:00
f0d9ca09aa fix: init_db兼容旧数据库,自动添加缺失的系统配置字段 2026-04-28 12:57:33 +08:00
36801e9266 feat: 我的页面和关于页面绑定后台系统配置 2026-04-28 12:48:51 +08:00
5acd9f08f1 fix: 发现智能体搜索支持基础智能体 2026-04-28 11:04:28 +08:00
31732a6303 fix: 历史使用左边标题右边智能体名字 2026-04-28 10:57:57 +08:00
7f576827b0 fix: 历史使用记录左边智能体名字右边标题 2026-04-28 10:55:15 +08:00
8287be10ea feat: 智能体对话标题自动更新+历史显示优化 2026-04-28 10:49:33 +08:00
a56bad11f1 feat: 用户头像在对话界面正确显示 2026-04-28 09:13:49 +08:00
92c187d576 fix: 智能体对话界面不显示工具选择按钮 2026-04-28 00:09:58 +08:00
9eeeace88c fix: 工具选择状态记忆 + 过滤联网搜索 + 默认未启用 2026-04-27 23:56:36 +08:00
ba5d49005b feat: 添加更多工具按钮,支持多选工具 2026-04-27 23:44:37 +08:00
c71f27072a feat: 系统设置可配置版本信息、技术基础、开发者等 2026-04-27 23:32:37 +08:00
22a109d6c0 feat: 用户头像上传功能 2026-04-27 20:02:40 +08:00
0244715a8a fix: LLM和搜索调用时记录统计到backend 2026-04-27 18:50:33 +08:00
1c3f7604c9 feat: 后台管理添加查看用户对话记录功能 2026-04-27 18:38:44 +08:00
0086eaa1d6 feat: 智能体数据同步到backend + 初始化时从backend加载完整数据 2026-04-27 18:33:15 +08:00
8e17ef5e15 fix: 登录时保存完整用户数据 2026-04-27 18:20:49 +08:00
773fb89b01 fix: 对话创建和消息发送时同步到backend 2026-04-27 18:20:06 +08:00
8199773ef6 feat: 用户对话数据同步到backend + 用户资料修改同步 2026-04-27 18:06:55 +08:00
d153986f3d fix: 隐藏注册页面验证码功能(暂时跳过验证) 2026-04-27 17:56:46 +08:00
4 changed files with 328 additions and 15 deletions

View File

@@ -46,6 +46,8 @@ def init_db():
model TEXT NOT NULL,
max_tokens INTEGER DEFAULT 2048,
temperature REAL DEFAULT 0.7,
enable_thinking INTEGER DEFAULT 0,
enable_vision INTEGER DEFAULT 0,
is_default INTEGER DEFAULT 0,
is_active INTEGER DEFAULT 1,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
@@ -53,6 +55,16 @@ def init_db():
)
''')
# 兼容旧数据库:添加缺失的字段
try:
cursor.execute('ALTER TABLE llm_configs ADD COLUMN enable_thinking INTEGER DEFAULT 0')
except:
pass
try:
cursor.execute('ALTER TABLE llm_configs ADD COLUMN enable_vision INTEGER DEFAULT 0')
except:
pass
# 智能体配置表
cursor.execute('''
CREATE TABLE IF NOT EXISTS agents (
@@ -601,10 +613,11 @@ def add_llm_config():
conn = get_db()
cursor = conn.cursor()
cursor.execute('''
INSERT INTO llm_configs (name, provider, api_url, api_key, model, max_tokens, temperature)
VALUES (?, ?, ?, ?, ?, ?, ?)
INSERT INTO llm_configs (name, provider, api_url, api_key, model, max_tokens, temperature, enable_thinking, enable_vision)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
''', (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()
config_id = cursor.lastrowid
conn.close()
@@ -619,9 +632,10 @@ def update_llm_config(id):
cursor = conn.cursor()
cursor.execute('''
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['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.close()
return jsonify({'success': True})

View File

@@ -414,8 +414,9 @@ async function loadLLMPage(content) {
<th>名称</th>
<th>提供商</th>
<th>模型</th>
<th>思考模式</th>
<th>视觉能力</th>
<th>API URL</th>
<th>状态</th>
<th>操作</th>
</tr>
</thead>
@@ -425,8 +426,9 @@ async function loadLLMPage(content) {
<td>${c.name} ${c.is_default ? '<span class="default-badge">默认</span>' : ''}</td>
<td>${c.provider}</td>
<td>${c.model}</td>
<td>${c.enable_thinking ? '✅ 支持' : '❌ 不支持'}</td>
<td>${c.enable_vision ? '✅ 支持' : '❌ 不支持'}</td>
<td style="max-width: 200px; overflow: hidden; text-overflow: ellipsis;">${c.api_url}</td>
<td>${c.is_active ? '✅ 启用' : '❌ 禁用'}</td>
<td>
<div class="action-btns">
<button class="action-btn edit" onclick="showEditLLMModal(${c.id})">编辑</button>
@@ -477,6 +479,18 @@ function showAddLLMModal() {
<label class="form-label">Temperature</label>
<input type="number" class="form-input" id="llmTemperature" value="0.7" step="0.1">
</div>
<div class="form-group">
<label style="display: flex; align-items: center; gap: 8px;">
<input type="checkbox" id="llmEnableThinking"> 支持思考模式(深度思考)
</label>
<span style="color: #999; font-size: 12px;">模型是否支持原生思考功能如DeepSeek的think标签</span>
</div>
<div class="form-group">
<label style="display: flex; align-items: center; gap: 8px;">
<input type="checkbox" id="llmEnableVision"> 支持视觉能力(图片分析)
</label>
<span style="color: #999; font-size: 12px;">模型是否支持图片输入和分析</span>
</div>
<button class="form-submit" onclick="saveLLM()">保存</button>
`);
}
@@ -519,6 +533,18 @@ function showEditLLMModal(id) {
<label class="form-label">Temperature</label>
<input type="number" class="form-input" id="llmTemperature" value="${config.temperature}" step="0.1">
</div>
<div class="form-group">
<label style="display: flex; align-items: center; gap: 8px;">
<input type="checkbox" id="llmEnableThinking" ${config.enable_thinking ? 'checked' : ''}> 支持思考模式(深度思考)
</label>
<span style="color: #999; font-size: 12px;">模型是否支持原生思考功能如DeepSeek的think标签</span>
</div>
<div class="form-group">
<label style="display: flex; align-items: center; gap: 8px;">
<input type="checkbox" id="llmEnableVision" ${config.enable_vision ? 'checked' : ''}> 支持视觉能力(图片分析)
</label>
<span style="color: #999; font-size: 12px;">模型是否支持图片输入和分析</span>
</div>
<button class="form-submit" onclick="updateLLM(${id})">保存</button>
`);
}
@@ -531,7 +557,9 @@ async function saveLLM() {
api_key: document.getElementById('llmApiKey').value,
model: document.getElementById('llmModel').value,
max_tokens: parseInt(document.getElementById('llmMaxTokens').value),
temperature: parseFloat(document.getElementById('llmTemperature').value)
temperature: parseFloat(document.getElementById('llmTemperature').value),
enable_thinking: document.getElementById('llmEnableThinking').checked ? 1 : 0,
enable_vision: document.getElementById('llmEnableVision').checked ? 1 : 0
};
if (!data.name || !data.api_url || !data.api_key || !data.model) {
@@ -553,7 +581,9 @@ async function updateLLM(id) {
api_key: document.getElementById('llmApiKey').value,
model: document.getElementById('llmModel').value,
max_tokens: parseInt(document.getElementById('llmMaxTokens').value),
temperature: parseFloat(document.getElementById('llmTemperature').value)
temperature: parseFloat(document.getElementById('llmTemperature').value),
enable_thinking: document.getElementById('llmEnableThinking').checked ? 1 : 0,
enable_vision: document.getElementById('llmEnableVision').checked ? 1 : 0
};
await fetchAPI(`/api/admin/llm/${id}`, 'PUT', data);

View File

@@ -78,7 +78,7 @@ function getTodayDate() {
// 从后台API加载配置
async function loadBackendConfig() {
try {
// 使用相对路径,自动适当前域名
// 使用相对路径,自动适当前域名
const API_BASE = window.location.origin;
const res = await fetch(`${API_BASE}/api/config`);
backendConfig = await res.json();
@@ -116,6 +116,13 @@ async function loadBackendConfig() {
};
}
// 加载LLM能力配置思考模式、视觉能力
if (backendConfig.llm) {
llmCapabilities.thinking = backendConfig.llm.enable_thinking === 1;
llmCapabilities.vision = backendConfig.llm.enable_vision === 1;
console.log('LLM能力: 思考模式=', llmCapabilities.thinking, '视觉=', llmCapabilities.vision);
}
updateAgentsDisplay();
console.log('后台配置已加载', backendConfig);
} catch (e) {
@@ -283,6 +290,12 @@ let enableThinking = false; // 深度思考
let enableSearch = false; // 联网搜索
let autoScrollEnabled = true; // 自动滚动(用户滚动后可关闭)
// LLM 能力标志(从后台配置加载)
let llmCapabilities = {
thinking: false, // 是否支持思考模式
vision: false // 是否支持视觉能力
};
// DOM 元素(初始为 null在 openConversation 时重新获取)
let appContainer = null;
let messagesContainer = null;
@@ -2917,10 +2930,12 @@ function showAgentChatPage() {
<!-- 功能开关栏 -->
<div class="feature-bar" id="featureBar">
<div class="feature-left">
${llmCapabilities.thinking ? `
<button class="feature-btn thinking-btn" id="thinkingBtn">
<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/></svg>
<span>深度思考</span>
</button>
` : ''}
<button class="feature-btn search-btn" id="searchBtn">
<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 7 9.5 7 14 9.01 14 9.5 11.99 14 9.5 14z"/></svg>
<span>联网搜索</span>
@@ -2940,7 +2955,7 @@ function showAgentChatPage() {
<button class="attach-btn" id="attachBtn" title="上传文件">
<svg viewBox="0 0 24 24" width="24" height="24"><path fill="currentColor" d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/></svg>
</button>
<textarea id="userInput" placeholder="输入消息..." rows="1"></textarea>
<textarea id="userInput" placeholder="${llmCapabilities.vision ? '输入消息或上传图片...' : '输入消息...'}" rows="1"></textarea>
<button class="send-btn" id="sendBtn">
<svg viewBox="0 0 24 24" width="24" height="24"><path fill="currentColor" d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg>
</button>
@@ -2949,10 +2964,12 @@ function showAgentChatPage() {
<!-- 上传选项弹窗 -->
<div class="attach-panel" id="attachPanel">
<div class="attach-panel-content">
${llmCapabilities.vision ? `
<div class="attach-item" data-type="image">
<div class="attach-icon">📷</div>
<div class="attach-label">上传图片</div>
</div>
` : ''}
<div class="attach-item" data-type="file">
<div class="attach-icon">📄</div>
<div class="attach-label">上传文件</div>
@@ -3385,10 +3402,12 @@ function openConversation(id) {
<!-- 功能开关栏 -->
<div class="feature-bar">
<div class="feature-left">
${llmCapabilities.thinking ? `
<button class="feature-btn thinking-btn ${enableThinking ? 'active' : ''}" id="thinkingBtn">
<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/></svg>
<span>深度思考</span>
</button>
` : ''}
<button class="feature-btn search-btn ${enableSearch ? 'active' : ''}" id="searchBtn">
<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 7 9.5 7 14 9.01 14 9.5 11.99 14 9.5 14z"/></svg>
<span>联网搜索</span>
@@ -3410,7 +3429,7 @@ function openConversation(id) {
</button>
<textarea
id="userInput"
placeholder="输入消息..."
placeholder="${llmCapabilities.vision ? '输入消息或上传图片...' : '输入消息...'}"
rows="1"
></textarea>
<button class="send-btn" id="sendBtn">
@@ -3421,10 +3440,12 @@ function openConversation(id) {
<!-- 上传选项弹窗 -->
<div class="attach-panel" id="attachPanel">
<div class="attach-panel-content">
${llmCapabilities.vision ? `
<div class="attach-item" data-type="image">
<div class="attach-icon">📷</div>
<div class="attach-label">上传图片</div>
</div>
` : ''}
<div class="attach-item" data-type="file">
<div class="attach-icon">📄</div>
<div class="attach-label">上传文件</div>
@@ -4286,6 +4307,13 @@ async function handleImageUpload(e) {
const file = e.target.files[0];
if (!file) return;
// 检查是否支持视觉能力
if (!llmCapabilities.vision) {
showToast('当前大模型不支持图片分析功能,请等待后期升级');
e.target.value = '';
return;
}
// 读取图片为base64
const reader = new FileReader();
reader.onload = async (event) => {

View File

@@ -1049,6 +1049,31 @@ body {
box-shadow: 0 0 8px rgba(102, 126, 234, 0.3);
}
.avatar-upload-section {
display: flex;
justify-content: center;
margin-bottom: 16px;
}
.avatar-upload-btn {
display: flex;
align-items: center;
gap: 8px;
padding: 12px 24px;
background: linear-gradient(135deg, var(--primary) 0%, #764ba2 100%);
color: white;
border: none;
border-radius: 10px;
font-size: 14px;
cursor: pointer;
transition: all 0.2s;
}
.avatar-upload-btn:hover {
transform: scale(1.02);
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
}
.edit-input-group {
margin-bottom: 16px;
}
@@ -1705,6 +1730,16 @@ body {
color: var(--primary);
}
.recent-agent-title {
font-size: 14px;
color: #333;
font-weight: 500;
max-width: 120px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.recent-agent-agent-name {
font-size: 12px;
color: var(--primary);
@@ -1782,7 +1817,17 @@ body {
color: var(--primary);
}
.agent-history-agent {
.agent-history-title {
font-size: 14px;
color: #333;
font-weight: 500;
max-width: 150px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.agent-history-agent-name {
font-size: 12px;
color: var(--primary);
background: rgba(102, 126, 234, 0.1);
@@ -2350,7 +2395,8 @@ body {
font-size: 20px;
}
.clear-btn {
/* TTS 语音播放按钮 */
.tts-btn {
background: rgba(255,255,255,0.2);
border: none;
border-radius: 8px;
@@ -2359,10 +2405,18 @@ body {
cursor: pointer;
}
.clear-btn:active {
.tts-btn:hover {
background: rgba(255,255,255,0.3);
}
.tts-btn.active {
background: rgba(255,255,255,0.4);
}
.tts-btn svg {
display: block;
}
.messages-container {
flex: 1;
overflow-y: auto;
@@ -2715,6 +2769,193 @@ body {
flex-shrink: 0;
}
/* 更多工具按钮 */
.tools-btn {
position: relative;
}
.tools-count {
position: absolute;
top: -4px;
right: -4px;
min-width: 16px;
height: 16px;
padding: 0 4px;
background: #ef4444;
color: white;
font-size: 11px;
font-weight: 600;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
}
/* 工具选择弹窗 */
.tools-popup {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.tools-popup-content {
width: 90%;
max-width: 400px;
background: white;
border-radius: 16px;
overflow: hidden;
}
.tools-popup-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 20px;
background: linear-gradient(135deg, var(--primary) 0%, #764ba2 100%);
color: white;
}
.tools-popup-header h3 {
margin: 0;
font-size: 18px;
}
.tools-popup-close {
width: 28px;
height: 28px;
display: flex;
align-items: center;
justify-content: center;
background: rgba(255, 255, 255, 0.2);
border: none;
border-radius: 14px;
color: white;
font-size: 20px;
cursor: pointer;
transition: background 0.2s;
}
.tools-popup-close:hover {
background: rgba(255, 255, 255, 0.3);
}
.tools-popup-body {
padding: 16px 20px;
}
.tools-popup-tip {
color: #718096;
font-size: 13px;
margin-bottom: 16px;
}
.tools-list {
display: flex;
flex-direction: column;
gap: 12px;
max-height: 300px;
overflow-y: auto;
}
.tool-option {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 16px;
background: #f5f7fa;
border: 2px solid transparent;
border-radius: 12px;
cursor: pointer;
transition: all 0.2s;
}
.tool-option:hover {
background: #e8ecf1;
}
.tool-option.selected {
background: rgba(102, 126, 234, 0.1);
border-color: var(--primary);
}
.tool-checkbox {
flex-shrink: 0;
color: #718096;
}
.tool-option.selected .tool-checkbox {
color: var(--primary);
}
.tool-icon {
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
background: white;
border-radius: 10px;
font-size: 20px;
}
.tool-info {
flex: 1;
}
.tool-name {
font-weight: 500;
color: #2d3748;
}
.tool-type {
font-size: 12px;
color: #718096;
margin-top: 2px;
}
.tools-popup-footer {
display: flex;
gap: 12px;
padding: 16px 20px;
background: #f5f7fa;
}
.tools-popup-btn {
flex: 1;
padding: 12px;
border: none;
border-radius: 10px;
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
}
.tools-popup-btn.cancel {
background: #e2e8f0;
color: #4a5568;
}
.tools-popup-btn.cancel:hover {
background: #cbd5e0;
}
.tools-popup-btn.confirm {
background: linear-gradient(135deg, var(--primary) 0%, #764ba2 100%);
color: white;
}
.tools-popup-btn.confirm:hover {
transform: scale(1.02);
}
/* 导航按钮样式 */
.nav-btn {
padding: 6px 8px;