Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 60db170c0d | |||
| 336e3cd12f | |||
| f0d9ca09aa | |||
| 36801e9266 | |||
| 5acd9f08f1 | |||
| 31732a6303 | |||
| 7f576827b0 | |||
| 8287be10ea | |||
| a56bad11f1 | |||
| 92c187d576 | |||
| 9eeeace88c |
@@ -276,6 +276,20 @@ def init_db():
|
|||||||
]
|
]
|
||||||
for key, value, desc in default_configs:
|
for key, value, desc in default_configs:
|
||||||
cursor.execute('INSERT INTO system_configs (key, value, description) VALUES (?, ?, ?)', (key, value, desc))
|
cursor.execute('INSERT INTO system_configs (key, value, description) VALUES (?, ?, ?)', (key, value, desc))
|
||||||
|
else:
|
||||||
|
# 检查并添加缺失的配置项(兼容旧数据库)
|
||||||
|
default_configs = [
|
||||||
|
('app_developer', 'OpenClaw Team', '开发者'),
|
||||||
|
('app_update_date', '2026-04-27', '更新日期'),
|
||||||
|
('app_technology', '智谱 GLM-4.5-Air 大模型', '技术基础'),
|
||||||
|
('app_description', '提供智能对话、多种智能体服务', '应用简介'),
|
||||||
|
('privacy_policy_url', '', '隐私政策链接'),
|
||||||
|
('user_agreement_url', '', '用户协议链接'),
|
||||||
|
]
|
||||||
|
for key, value, desc in default_configs:
|
||||||
|
cursor.execute('SELECT COUNT(*) FROM system_configs WHERE key=?', (key,))
|
||||||
|
if cursor.fetchone()[0] == 0:
|
||||||
|
cursor.execute('INSERT INTO system_configs (key, value, description) VALUES (?, ?, ?)', (key, value, desc))
|
||||||
|
|
||||||
# 初始化管理员账户
|
# 初始化管理员账户
|
||||||
cursor.execute('SELECT COUNT(*) FROM admin_users')
|
cursor.execute('SELECT COUNT(*) FROM admin_users')
|
||||||
@@ -487,8 +501,9 @@ def get_users():
|
|||||||
|
|
||||||
|
|
||||||
@app.route('/api/admin/users/<int:id>', methods=['GET'])
|
@app.route('/api/admin/users/<int:id>', methods=['GET'])
|
||||||
|
@app.route('/api/user/<int:id>', methods=['GET'])
|
||||||
def get_user(id):
|
def get_user(id):
|
||||||
"""获取单个用户"""
|
"""获取单个用户信息"""
|
||||||
conn = get_db()
|
conn = get_db()
|
||||||
cursor = conn.cursor()
|
cursor = conn.cursor()
|
||||||
cursor.execute('SELECT * FROM users WHERE id = ?', (id,))
|
cursor.execute('SELECT * FROM users WHERE id = ?', (id,))
|
||||||
|
|||||||
144
www/app.js
144
www/app.js
@@ -118,12 +118,23 @@ async function loadBackendConfig() {
|
|||||||
|
|
||||||
// 加载工具列表
|
// 加载工具列表
|
||||||
if (backendConfig.allTools) {
|
if (backendConfig.allTools) {
|
||||||
allTools = backendConfig.allTools;
|
// 过滤掉联网搜索(已有单独按钮)
|
||||||
|
allTools = backendConfig.allTools.filter(t => t.type !== 'search');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载默认启用的工具
|
// 不加载默认启用的工具,所有工具默认未启用
|
||||||
if (backendConfig.tools) {
|
|
||||||
enabledTools = backendConfig.tools.map(t => t.tool_id);
|
// 将后台系统配置赋值到 CONFIG
|
||||||
|
if (backendConfig.system) {
|
||||||
|
CONFIG.system = backendConfig.system;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 将后台 LLM 配置赋值到 CONFIG
|
||||||
|
if (backendConfig.llm) {
|
||||||
|
CONFIG.apiUrl = backendConfig.llm.api_url;
|
||||||
|
CONFIG.apiKey = backendConfig.llm.api_key;
|
||||||
|
CONFIG.model = backendConfig.llm.model;
|
||||||
|
CONFIG.maxTokens = backendConfig.llm.max_tokens || 2048;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateAgentsDisplay();
|
updateAgentsDisplay();
|
||||||
@@ -310,15 +321,41 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||||||
// 初始化 appContainer
|
// 初始化 appContainer
|
||||||
appContainer = document.getElementById('app');
|
appContainer = document.getElementById('app');
|
||||||
|
|
||||||
|
// 加载后台配置(包含 LLM、智能体等)
|
||||||
|
await loadBackendConfig();
|
||||||
|
|
||||||
// 加载用户登录状态(优先检查)
|
// 加载用户登录状态(优先检查)
|
||||||
const savedUser = localStorage.getItem('currentUser');
|
const savedUser = localStorage.getItem('currentUser');
|
||||||
if (savedUser) {
|
if (savedUser) {
|
||||||
currentUser = JSON.parse(savedUser);
|
currentUser = JSON.parse(savedUser);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果用户已登录且有ID,从 backend 加载对话数据
|
// 如果用户已登录且有ID,从 backend 加载最新用户信息和对话数据
|
||||||
if (currentUser && currentUser.id) {
|
if (currentUser && currentUser.id) {
|
||||||
try {
|
try {
|
||||||
|
// 从后台获取最新用户信息(包括头像)
|
||||||
|
const userRes = await fetch(`/api/user/${currentUser.id}`);
|
||||||
|
if (userRes.ok) {
|
||||||
|
const userData = await userRes.json();
|
||||||
|
if (userData && userData.id) {
|
||||||
|
// 更新 currentUser 为后台最新数据
|
||||||
|
currentUser = {
|
||||||
|
id: userData.id,
|
||||||
|
username: userData.username,
|
||||||
|
phone: userData.phone || '',
|
||||||
|
email: userData.email || '',
|
||||||
|
avatar: userData.avatar || '👤',
|
||||||
|
signature: userData.signature || '',
|
||||||
|
gender: userData.gender || '',
|
||||||
|
age: userData.age || '',
|
||||||
|
region: userData.region || '',
|
||||||
|
registeredAt: Date.now()
|
||||||
|
};
|
||||||
|
saveCurrentUser();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从 backend 加载对话数据
|
||||||
const res = await fetch(`/api/user/${currentUser.id}/conversations`);
|
const res = await fetch(`/api/user/${currentUser.id}/conversations`);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
if (Array.isArray(data) && data.length > 0) {
|
if (Array.isArray(data) && data.length > 0) {
|
||||||
@@ -426,13 +463,8 @@ document.addEventListener('DOMContentLoaded', async () => {
|
|||||||
currentPage = savedPage;
|
currentPage = savedPage;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载后台配置并显示主页
|
// 显示主页
|
||||||
loadBackendConfig().then(() => {
|
|
||||||
showMainPage();
|
showMainPage();
|
||||||
}).catch(() => {
|
|
||||||
// 即使加载失败也显示主页
|
|
||||||
showMainPage();
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 根据用户配置更新显示的智能体
|
// 根据用户配置更新显示的智能体
|
||||||
@@ -791,7 +823,7 @@ function renderAgentsPage() {
|
|||||||
<div class="recent-agent-item" data-conv-id="${conv.id}">
|
<div class="recent-agent-item" data-conv-id="${conv.id}">
|
||||||
<div class="recent-agent-left">
|
<div class="recent-agent-left">
|
||||||
<span class="recent-agent-avatar">${conv.agent ? conv.agent.avatar : '🤖'}</span>
|
<span class="recent-agent-avatar">${conv.agent ? conv.agent.avatar : '🤖'}</span>
|
||||||
<span class="recent-agent-name">${conv.title}</span>
|
<span class="recent-agent-title">${conv.title}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="recent-agent-right">
|
<div class="recent-agent-right">
|
||||||
<span class="recent-agent-agent-name">${conv.agent ? conv.agent.name : '未知智能体'}</span>
|
<span class="recent-agent-agent-name">${conv.agent ? conv.agent.name : '未知智能体'}</span>
|
||||||
@@ -1326,6 +1358,7 @@ function filterDiscoverAgents(keyword) {
|
|||||||
if (!keyword) {
|
if (!keyword) {
|
||||||
// 显示所有
|
// 显示所有
|
||||||
showDiscoverSection('hot', systemAgents.filter(a => a.category === 'hot'));
|
showDiscoverSection('hot', systemAgents.filter(a => a.category === 'hot'));
|
||||||
|
showDiscoverSection('basic', systemAgents.filter(a => a.category === 'basic'));
|
||||||
showDiscoverSection('work', systemAgents.filter(a => a.category === 'work'));
|
showDiscoverSection('work', systemAgents.filter(a => a.category === 'work'));
|
||||||
showDiscoverSection('study', systemAgents.filter(a => a.category === 'study'));
|
showDiscoverSection('study', systemAgents.filter(a => a.category === 'study'));
|
||||||
showDiscoverSection('life', systemAgents.filter(a => a.category === 'life'));
|
showDiscoverSection('life', systemAgents.filter(a => a.category === 'life'));
|
||||||
@@ -1340,6 +1373,7 @@ function filterDiscoverAgents(keyword) {
|
|||||||
|
|
||||||
// 显示搜索结果
|
// 显示搜索结果
|
||||||
showDiscoverSection('hot', filtered.filter(a => a.category === 'hot'));
|
showDiscoverSection('hot', filtered.filter(a => a.category === 'hot'));
|
||||||
|
showDiscoverSection('basic', filtered.filter(a => a.category === 'basic'));
|
||||||
showDiscoverSection('work', filtered.filter(a => a.category === 'work'));
|
showDiscoverSection('work', filtered.filter(a => a.category === 'work'));
|
||||||
showDiscoverSection('study', filtered.filter(a => a.category === 'study'));
|
showDiscoverSection('study', filtered.filter(a => a.category === 'study'));
|
||||||
showDiscoverSection('life', filtered.filter(a => a.category === 'life'));
|
showDiscoverSection('life', filtered.filter(a => a.category === 'life'));
|
||||||
@@ -2613,7 +2647,12 @@ function showAboutPage() {
|
|||||||
const privacyPolicyBtn = document.getElementById('privacyPolicyBtn');
|
const privacyPolicyBtn = document.getElementById('privacyPolicyBtn');
|
||||||
if (privacyPolicyBtn) {
|
if (privacyPolicyBtn) {
|
||||||
privacyPolicyBtn.addEventListener('click', () => {
|
privacyPolicyBtn.addEventListener('click', () => {
|
||||||
showToast('隐私政策页面待完善');
|
const privacyUrl = CONFIG?.system?.privacyPolicyUrl;
|
||||||
|
if (privacyUrl) {
|
||||||
|
window.open(privacyUrl, '_blank');
|
||||||
|
} else {
|
||||||
|
showToast('隐私政策链接未配置');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2621,7 +2660,12 @@ function showAboutPage() {
|
|||||||
const userAgreementBtn = document.getElementById('userAgreementBtn');
|
const userAgreementBtn = document.getElementById('userAgreementBtn');
|
||||||
if (userAgreementBtn) {
|
if (userAgreementBtn) {
|
||||||
userAgreementBtn.addEventListener('click', () => {
|
userAgreementBtn.addEventListener('click', () => {
|
||||||
showToast('用户协议页面待完善');
|
const agreementUrl = CONFIG?.system?.userAgreementUrl;
|
||||||
|
if (agreementUrl) {
|
||||||
|
window.open(agreementUrl, '_blank');
|
||||||
|
} else {
|
||||||
|
showToast('用户协议链接未配置');
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3005,10 +3049,10 @@ function showAgentHistoryPage() {
|
|||||||
<div class="agent-history-item" data-conv-id="${conv.id}">
|
<div class="agent-history-item" data-conv-id="${conv.id}">
|
||||||
<div class="agent-history-left">
|
<div class="agent-history-left">
|
||||||
<span class="agent-history-avatar">${conv.agent ? conv.agent.avatar : '🤖'}</span>
|
<span class="agent-history-avatar">${conv.agent ? conv.agent.avatar : '🤖'}</span>
|
||||||
<span class="agent-history-name">${conv.title}</span>
|
<span class="agent-history-title">${conv.title}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="agent-history-right">
|
<div class="agent-history-right">
|
||||||
<span class="agent-history-agent">${conv.agent ? conv.agent.name : '未知智能体'}</span>
|
<span class="agent-history-agent-name">${conv.agent ? conv.agent.name : '未知智能体'}</span>
|
||||||
<span class="agent-history-time">${formatTime(conv.updatedAt)}</span>
|
<span class="agent-history-time">${formatTime(conv.updatedAt)}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -3044,6 +3088,9 @@ async function openAgent(agentId) {
|
|||||||
currentAgent = systemAgents.find(a => a.id === agentId);
|
currentAgent = systemAgents.find(a => a.id === agentId);
|
||||||
if (!currentAgent) return;
|
if (!currentAgent) return;
|
||||||
|
|
||||||
|
// 加载该智能体的工具选择状态
|
||||||
|
loadToolsState();
|
||||||
|
|
||||||
// 检查未登录用户的智能体限制
|
// 检查未登录用户的智能体限制
|
||||||
if (!currentUser) {
|
if (!currentUser) {
|
||||||
const check = canUseAgent(agentId);
|
const check = canUseAgent(agentId);
|
||||||
@@ -3086,7 +3133,7 @@ async function openAgent(agentId) {
|
|||||||
// 创建新对话并设置智能体
|
// 创建新对话并设置智能体
|
||||||
const newConv = {
|
const newConv = {
|
||||||
id: backendId || Date.now().toString(),
|
id: backendId || Date.now().toString(),
|
||||||
title: currentAgent.name,
|
title: '新对话', // 和普通对话一样,初始标题为"新对话",后续自动生成
|
||||||
messages: [],
|
messages: [],
|
||||||
agentId: agentId,
|
agentId: agentId,
|
||||||
createdAt: Date.now(),
|
createdAt: Date.now(),
|
||||||
@@ -3130,22 +3177,10 @@ function showAgentChatPage() {
|
|||||||
<div class="messages" id="messages"></div>
|
<div class="messages" id="messages"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 功能开关栏 -->
|
<!-- 功能栏(智能体对话不需要工具选择,工具由后台配置) -->
|
||||||
<div class="feature-bar" id="featureBar">
|
<div class="feature-bar" id="featureBar">
|
||||||
<div class="feature-left">
|
<div class="feature-left">
|
||||||
<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>
|
|
||||||
</button>
|
|
||||||
<button class="feature-btn tools-btn" id="moreToolsBtn">
|
|
||||||
<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M22.7 19l-9.1-9.1c.9-2.3.4-5-1.5-6.9-2-2-5-2.4-7.4-1.3L9 6 6 9 1.6 4.7C.4 7.1.9 10.1 2.9 12.1c1.9 1.9 4.6 2.4 6.9 1.5l9.1 9.1c.4.4 1 .4 1.4 0l2.3-2.3c.5-.4.5-1.1.1-1.4z"/></svg>
|
|
||||||
<span>更多工具</span>
|
|
||||||
${enabledTools.length > 0 ? `<span class="tools-count">${enabledTools.length}</span>` : ''}
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="feature-right">
|
<div class="feature-right">
|
||||||
<button class="feature-btn nav-btn" id="scrollTopBtn">
|
<button class="feature-btn nav-btn" id="scrollTopBtn">
|
||||||
@@ -3599,6 +3634,10 @@ function openConversation(id) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 普通对话,加载普通对话的工具状态
|
||||||
|
currentAgent = null;
|
||||||
|
loadToolsState();
|
||||||
|
|
||||||
// 渲染对话页面
|
// 渲染对话页面
|
||||||
const chatHtml = `
|
const chatHtml = `
|
||||||
<div id="chatPage">
|
<div id="chatPage">
|
||||||
@@ -4232,6 +4271,9 @@ function confirmToolsSelection() {
|
|||||||
|
|
||||||
enabledTools = selectedTools;
|
enabledTools = selectedTools;
|
||||||
|
|
||||||
|
// 保存工具选择状态
|
||||||
|
saveToolsState();
|
||||||
|
|
||||||
// 更新按钮显示
|
// 更新按钮显示
|
||||||
const moreToolsBtn = document.getElementById('moreToolsBtn');
|
const moreToolsBtn = document.getElementById('moreToolsBtn');
|
||||||
if (moreToolsBtn) {
|
if (moreToolsBtn) {
|
||||||
@@ -4254,6 +4296,38 @@ function confirmToolsSelection() {
|
|||||||
showToast(`已启用 ${enabledTools.length} 个工具`);
|
showToast(`已启用 ${enabledTools.length} 个工具`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 保存工具选择状态
|
||||||
|
function saveToolsState() {
|
||||||
|
if (currentAgent) {
|
||||||
|
// 智能体对话:保存到 localStorage(按智能体ID)
|
||||||
|
localStorage.setItem(`agentEnabledTools_${currentAgent.id}`, JSON.stringify(enabledTools));
|
||||||
|
} else {
|
||||||
|
// 普通对话:保存到 localStorage
|
||||||
|
localStorage.setItem('chatEnabledTools', JSON.stringify(enabledTools));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加载工具选择状态
|
||||||
|
function loadToolsState() {
|
||||||
|
if (currentAgent) {
|
||||||
|
// 智能体对话:加载该智能体的工具状态
|
||||||
|
const saved = localStorage.getItem(`agentEnabledTools_${currentAgent.id}`);
|
||||||
|
if (saved) {
|
||||||
|
enabledTools = JSON.parse(saved);
|
||||||
|
} else {
|
||||||
|
enabledTools = [];
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 普通对话:加载普通对话的工具状态
|
||||||
|
const saved = localStorage.getItem('chatEnabledTools');
|
||||||
|
if (saved) {
|
||||||
|
enabledTools = JSON.parse(saved);
|
||||||
|
} else {
|
||||||
|
enabledTools = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 渲染头像(支持 emoji 和上传的图片)
|
// 渲染头像(支持 emoji 和上传的图片)
|
||||||
function renderAvatar(avatar) {
|
function renderAvatar(avatar) {
|
||||||
if (!avatar) return '👤';
|
if (!avatar) return '👤';
|
||||||
@@ -4513,7 +4587,11 @@ function renderMessages() {
|
|||||||
|
|
||||||
messagesDiv.innerHTML = currentConversation.messages.map((msg, index) => {
|
messagesDiv.innerHTML = currentConversation.messages.map((msg, index) => {
|
||||||
const isUser = msg.role === 'user';
|
const isUser = msg.role === 'user';
|
||||||
const avatar = isUser ? '👤' : '🤖';
|
// 用户头像使用实际头像,AI头像使用智能体头像或默认
|
||||||
|
const avatar = isUser
|
||||||
|
? (currentUser?.avatar || '👤')
|
||||||
|
: (currentAgent?.avatar || '🤖');
|
||||||
|
const avatarHtml = isUser ? renderAvatar(avatar) : avatar;
|
||||||
|
|
||||||
// 处理消息内容(支持图片)
|
// 处理消息内容(支持图片)
|
||||||
let contentHtml = '';
|
let contentHtml = '';
|
||||||
@@ -4588,7 +4666,7 @@ function renderMessages() {
|
|||||||
|
|
||||||
return `
|
return `
|
||||||
<div class="message ${msg.role}" data-index="${index}">
|
<div class="message ${msg.role}" data-index="${index}">
|
||||||
<div class="message-avatar">${avatar}</div>
|
<div class="message-avatar">${avatarHtml}</div>
|
||||||
<div class="message-body">
|
<div class="message-body">
|
||||||
${searchHtml}
|
${searchHtml}
|
||||||
${thinkingHtml}
|
${thinkingHtml}
|
||||||
|
|||||||
@@ -1730,6 +1730,16 @@ body {
|
|||||||
color: var(--primary);
|
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 {
|
.recent-agent-agent-name {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: var(--primary);
|
color: var(--primary);
|
||||||
@@ -1807,7 +1817,17 @@ body {
|
|||||||
color: var(--primary);
|
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;
|
font-size: 12px;
|
||||||
color: var(--primary);
|
color: var(--primary);
|
||||||
background: rgba(102, 126, 234, 0.1);
|
background: rgba(102, 126, 234, 0.1);
|
||||||
|
|||||||
Reference in New Issue
Block a user