Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7c8adc0d78 | |||
| 6fd916f57c |
@@ -54,6 +54,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,
|
||||
@@ -221,6 +223,14 @@ def init_db():
|
||||
'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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')
|
||||
if cursor.fetchone()[0] == 0:
|
||||
@@ -1005,10 +1015,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()
|
||||
@@ -1023,9 +1034,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})
|
||||
|
||||
@@ -401,6 +401,54 @@
|
||||
.toast.show {
|
||||
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>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
66
www/admin.js
66
www/admin.js
@@ -559,7 +559,8 @@ async function loadLLMPage(content) {
|
||||
<th>名称</th>
|
||||
<th>提供商</th>
|
||||
<th>模型</th>
|
||||
<th>API URL</th>
|
||||
<th>思考</th>
|
||||
<th>视觉</th>
|
||||
<th>状态</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
@@ -570,7 +571,8 @@ 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 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>
|
||||
<div class="action-btns">
|
||||
@@ -622,8 +624,28 @@ 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-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>
|
||||
`);
|
||||
|
||||
// 绑定开关事件
|
||||
bindToggleSwitch('llmThinkingToggle');
|
||||
bindToggleSwitch('llmVisionToggle');
|
||||
}
|
||||
|
||||
function showEditLLMModal(id) {
|
||||
@@ -664,8 +686,28 @@ 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-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>
|
||||
`);
|
||||
|
||||
// 绑定开关事件
|
||||
bindToggleSwitch('llmThinkingToggle');
|
||||
bindToggleSwitch('llmVisionToggle');
|
||||
}
|
||||
|
||||
async function saveLLM() {
|
||||
@@ -676,7 +718,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: 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) {
|
||||
@@ -698,7 +742,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: parseInt(document.getElementById('llmThinkingToggle')?.dataset.value || 0),
|
||||
enable_vision: parseInt(document.getElementById('llmVisionToggle')?.dataset.value || 0)
|
||||
};
|
||||
|
||||
await fetchAPI(`/api/admin/llm/${id}`, 'PUT', data);
|
||||
@@ -1512,6 +1558,18 @@ function closeModal() {
|
||||
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) {
|
||||
const toast = document.getElementById('toast');
|
||||
toast.textContent = message;
|
||||
|
||||
90
www/app.js
90
www/app.js
@@ -518,6 +518,10 @@ function savePinnedAgents() {
|
||||
// ==================== 主页(底部导航栏) ====================
|
||||
|
||||
function showMainPage() {
|
||||
// 退出对话界面时停止语音播放
|
||||
stopTTSQueue();
|
||||
enableTTS = false;
|
||||
|
||||
currentConversation = null;
|
||||
currentAgent = null;
|
||||
|
||||
@@ -4817,9 +4821,18 @@ function cleanTTSText(text) {
|
||||
// 移除数字列表(1.、2.等)
|
||||
cleaned = cleaned.replace(/^\d+\.\s+/gm, '');
|
||||
|
||||
// 移除任务列表([ ]、[x])
|
||||
cleaned = cleaned.replace(/^\s*\[[x ]\]\s*/gmi, '');
|
||||
|
||||
// 处理图片语法  -> 移除整个图片标记
|
||||
cleaned = cleaned.replace(/!\[[^\]]*\]\([^)]+\)/g, '');
|
||||
|
||||
// 处理链接 [text](url) -> 只保留text
|
||||
cleaned = cleaned.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1');
|
||||
|
||||
// 移除删除线(~~text~~)
|
||||
cleaned = cleaned.replace(/~~([^~]+)~~/g, '$1');
|
||||
|
||||
// 移除粗体/斜体符号(**text**、*text*、__text__、_text_)
|
||||
cleaned = cleaned.replace(/\*\*([^*]+)\*\*/g, '$1');
|
||||
cleaned = cleaned.replace(/\*([^*]+)\*/g, '$1');
|
||||
@@ -4829,10 +4842,18 @@ function cleanTTSText(text) {
|
||||
// 移除引用符号(>)
|
||||
cleaned = cleaned.replace(/^>\s*/gm, '');
|
||||
|
||||
// 移除分割线(---、***)
|
||||
// 移除分割线(---、***、___)
|
||||
cleaned = cleaned.replace(/^[\-\*]{3,}$/gm, '');
|
||||
cleaned = cleaned.replace(/^_{3,}$/gm, '');
|
||||
|
||||
// 移除表情符号(常见emoji范围)
|
||||
// 移除表格分隔符(| 和 ---|--|--)
|
||||
cleaned = cleaned.replace(/^\|.*\|$/gm, ''); // 表格行
|
||||
cleaned = cleaned.replace(/^\s*[\-\:]+\s*\|[\s\-\:]+\|[\s\-\:]+\s*$/gm, ''); // 表格分隔行
|
||||
|
||||
// 移除脚注引用([^1])
|
||||
cleaned = cleaned.replace(/\[\^[^\]]+\]/g, '');
|
||||
|
||||
// 移除表情符号(完整emoji范围)
|
||||
cleaned = cleaned.replace(/[\u{1F600}-\u{1F64F}]/gu, ''); // 表情
|
||||
cleaned = cleaned.replace(/[\u{1F300}-\u{1F5FF}]/gu, ''); // 符号和图形
|
||||
cleaned = cleaned.replace(/[\u{1F680}-\u{1F6FF}]/gu, ''); // 交通和地图
|
||||
@@ -4846,11 +4867,74 @@ function cleanTTSText(text) {
|
||||
cleaned = cleaned.replace(/[\u{2700}-\u{27BF}]/gu, ''); // 装饰符号
|
||||
cleaned = cleaned.replace(/[\u{FE00}-\u{FE0F}]/gu, ''); // 变体选择符
|
||||
cleaned = cleaned.replace(/[\u{1F1E0}-\u{1F1FF}]/gu, ''); // 旗帜
|
||||
cleaned = cleaned.replace(/[\u{1F004}\u{1F0CF}]/gu, ''); // 麻将牌
|
||||
cleaned = cleaned.replace(/[\u{231A}-\u{231B}]/gu, ''); // 时钟
|
||||
cleaned = cleaned.replace(/[\u{23E9}-\u{23F3}]/gu, ''); // 其他符号
|
||||
cleaned = cleaned.replace(/[\u{23F8}-\u{23FA}]/gu, ''); // 暂停/播放等
|
||||
cleaned = cleaned.replace(/[\u{25AA}-\u{25AB}]/gu, ''); // 小方块
|
||||
cleaned = cleaned.replace(/[\u{25B6}]/gu, ''); // 播放按钮
|
||||
cleaned = cleaned.replace(/[\u{25C0}]/gu, ''); // 反向播放
|
||||
cleaned = cleaned.replace(/[\u{25FB}-\u{25FE}]/gu, ''); // 白色方块
|
||||
cleaned = cleaned.replace(/[\u{2614}-\u{2615}]/gu, ''); // 雨伞/咖啡
|
||||
cleaned = cleaned.replace(/[\u{2648}-\u{2653}]/gu, ''); // 星座符号
|
||||
cleaned = cleaned.replace(/[\u{267F}]/gu, ''); // 轮椅符号
|
||||
cleaned = cleaned.replace(/[\u{2693}]/gu, ''); // 船锚
|
||||
cleaned = cleaned.replace(/[\u{26A1}]/gu, ''); // 高压符号
|
||||
cleaned = cleaned.replace(/[\u{26AA}-\u{26AB}]/gu, ''); // 圆圈
|
||||
cleaned = cleaned.replace(/[\u{26BD}]/gu, ''); // 足球
|
||||
cleaned = cleaned.replace(/[\u{26BE}]/gu, ''); // 棒球
|
||||
cleaned = cleaned.replace(/[\u{26C4}]/gu, ''); // 雪人
|
||||
cleaned = cleaned.replace(/[\u{26C5}]/gu, ''); // 太阳云
|
||||
cleaned = cleaned.replace(/[\u{26CE}]/gu, ''); // 星座
|
||||
cleaned = cleaned.replace(/[\u{26D4}]/gu, ''); // 禁止进入
|
||||
cleaned = cleaned.replace(/[\u{26EA}]/gu, ''); // 教堂
|
||||
cleaned = cleaned.replace(/[\u{26F2}]/gu, ''); // 喷泉
|
||||
cleaned = cleaned.replace(/[\u{26F3}]/gu, ''); // 高尔夫
|
||||
cleaned = cleaned.replace(/[\u{26F5}]/gu, ''); // 帆船
|
||||
cleaned = cleaned.replace(/[\u{26FA}]/gu, ''); // 帐篷
|
||||
cleaned = cleaned.replace(/[\u{26FD}]/gu, ''); // 加油站
|
||||
cleaned = cleaned.replace(/[\u{2702}]/gu, ''); // 剪刀
|
||||
cleaned = cleaned.replace(/[\u{2705}]/gu, ''); // 白色对勾
|
||||
cleaned = cleaned.replace(/[\u{2708}-\u{270D}]/gu, ''); // 飞机/笔等
|
||||
cleaned = cleaned.replace(/[\u{270F}]/gu, ''); // 铅笔
|
||||
cleaned = cleaned.replace(/[\u{2712}]/gu, ''); // 笔
|
||||
cleaned = cleaned.replace(/[\u{2714}]/gu, ''); // 对勾
|
||||
cleaned = cleaned.replace(/[\u{2716}]/gu, ''); // X标记
|
||||
cleaned = cleaned.replace(/[\u{271D}]/gu, ''); // 十字架
|
||||
cleaned = cleaned.replace(/[\u{2721}]/gu, ''); // 六芒星
|
||||
cleaned = cleaned.replace(/[\u{2728}]/gu, ''); // 星光
|
||||
cleaned = cleaned.replace(/[\u{2733}-\u{2734}]/gu, ''); // 八角雪花
|
||||
cleaned = cleaned.replace(/[\u{2744}]/gu, ''); // 雪花
|
||||
cleaned = cleaned.replace(/[\u{2747}]/gu, ''); // 闪亮
|
||||
cleaned = cleaned.replace(/[\u{274C}]/gu, ''); // 红色X
|
||||
cleaned = cleaned.replace(/[\u{274E}]/gu, ''); // 红色方X
|
||||
cleaned = cleaned.replace(/[\u{2753}-\u{2755}]/gu, ''); // 问号
|
||||
cleaned = cleaned.replace(/[\u{2757}]/gu, ''); // 感叹号
|
||||
cleaned = cleaned.replace(/[\u{2763}-\u{2764}]/gu, ''); // 心形
|
||||
cleaned = cleaned.replace(/[\u{2795}-\u{2797}]/gu, ''); // 加减乘
|
||||
cleaned = cleaned.replace(/[\u{27A1}]/gu, ''); // 箭头
|
||||
cleaned = cleaned.replace(/[\u{27B0}]/gu, ''); // 曲线箭头
|
||||
cleaned = cleaned.replace(/[\u{27BF}]/gu, ''); // 双曲线箭头
|
||||
cleaned = cleaned.replace(/[\u{2934}-\u{2935}]/gu, ''); // 箭头
|
||||
cleaned = cleaned.replace(/[\u{2B05}-\u{2B07}]/gu, ''); // 方向箭头
|
||||
cleaned = cleaned.replace(/[\u{2B1B}-\u{2B1C}]/gu, ''); // 黑白方块
|
||||
cleaned = cleaned.replace(/[\u{2B50}]/gu, ''); // 中等星
|
||||
cleaned = cleaned.replace(/[\u{2B55}]/gu, ''); // 圆圈
|
||||
cleaned = cleaned.replace(/[\u{3030}]/gu, ''); // 波浪线
|
||||
cleaned = cleaned.replace(/[\u{303D}]/gu, ''); // 花括号
|
||||
cleaned = cleaned.replace(/[\u{3297}]/gu, ''); // 圆圈日文
|
||||
cleaned = cleaned.replace(/[\u{3299}]/gu, ''); // 圆圈日文
|
||||
|
||||
// 移除HTML实体
|
||||
cleaned = cleaned.replace(/&[a-zA-Z]+;/g, '');
|
||||
|
||||
// 清理多余空白
|
||||
// 移除特殊符号(数学、货币等)
|
||||
cleaned = cleaned.replace(/[≤≥≠≈∞∑∏√∫]/g, '');
|
||||
cleaned = cleaned.replace(/[€£¥₹₽₩]/g, '');
|
||||
cleaned = cleaned.replace(/[©®™]/g, '');
|
||||
|
||||
// 清理多余空白和换行
|
||||
cleaned = cleaned.replace(/\n+/g, ' ');
|
||||
cleaned = cleaned.replace(/\s+/g, ' ').trim();
|
||||
|
||||
return cleaned;
|
||||
|
||||
Reference in New Issue
Block a user