Compare commits

..

1 Commits

Author SHA1 Message Date
8199773ef6 feat: 用户对话数据同步到backend + 用户资料修改同步 2026-04-27 18:06:55 +08:00
2 changed files with 303 additions and 35 deletions

View File

@@ -166,6 +166,20 @@ def init_db():
) )
''') ''')
# 对话表(用户对话数据)
cursor.execute('''
CREATE TABLE IF NOT EXISTS conversations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
title TEXT NOT NULL,
agent_id TEXT,
messages TEXT NOT NULL DEFAULT '[]',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
)
''')
# 初始化默认大模型配置 # 初始化默认大模型配置
cursor.execute('SELECT COUNT(*) FROM llm_configs') cursor.execute('SELECT COUNT(*) FROM llm_configs')
if cursor.fetchone()[0] == 0: if cursor.fetchone()[0] == 0:
@@ -581,6 +595,118 @@ def change_user_password(id):
return jsonify({'success': True}) return jsonify({'success': True})
# ==================== 用户对话数据同步 ====================
@app.route('/api/user/<int:user_id>/conversations', methods=['GET'])
def get_user_conversations(user_id):
"""获取用户所有对话"""
conn = get_db()
cursor = conn.cursor()
cursor.execute('''
SELECT id, title, agent_id, messages, created_at, updated_at
FROM conversations WHERE user_id = ? ORDER BY updated_at DESC
''', (user_id,))
conversations = []
for row in cursor.fetchall():
conv = dict(row)
# 解析消息JSON
try:
conv['messages'] = json.loads(conv['messages']) if conv['messages'] else []
except:
conv['messages'] = []
# 转换为前端格式使用字符串ID
conv['id'] = str(conv['id'])
conv['createdAt'] = int(datetime.strptime(conv['created_at'], '%Y-%m-%d %H:%M:%S').timestamp() * 1000) if conv['created_at'] else 0
conv['updatedAt'] = int(datetime.strptime(conv['updated_at'], '%Y-%m-%d %H:%M:%S').timestamp() * 1000) if conv['updated_at'] else 0
conv['agentId'] = conv['agent_id']
conversations.append(conv)
conn.close()
return jsonify(conversations)
@app.route('/api/user/<int:user_id>/conversations', methods=['POST'])
def create_user_conversation(user_id):
"""创建新对话"""
data = request.json
title = data.get('title', '新对话')
agent_id = data.get('agentId') or data.get('agent_id')
messages = data.get('messages', [])
conn = get_db()
cursor = conn.cursor()
cursor.execute('''
INSERT INTO conversations (user_id, title, agent_id, messages)
VALUES (?, ?, ?, ?)
''', (user_id, title, agent_id, json.dumps(messages)))
conn.commit()
conv_id = cursor.lastrowid
conn.close()
return jsonify({'success': True, 'id': str(conv_id)})
@app.route('/api/user/<int:user_id>/conversations/<int:conv_id>', methods=['PUT'])
def update_user_conversation(user_id, conv_id):
"""更新对话(添加消息、修改标题等)"""
data = request.json
conn = get_db()
cursor = conn.cursor()
# 验证对话属于该用户
cursor.execute('SELECT id FROM conversations WHERE id = ? AND user_id = ?', (conv_id, user_id))
if not cursor.fetchone():
conn.close()
return jsonify({'error': '对话不存在'}), 404
# 更新字段
updates = []
values = []
if 'title' in data:
updates.append('title = ?')
values.append(data['title'])
if 'messages' in data:
updates.append('messages = ?')
values.append(json.dumps(data['messages']))
if 'agentId' in data or 'agent_id' in data:
updates.append('agent_id = ?')
values.append(data.get('agentId') or data.get('agent_id'))
if updates:
updates.append('updated_at = CURRENT_TIMESTAMP')
sql = f'UPDATE conversations SET {", ".join(updates)} WHERE id = ?'
values.append(conv_id)
cursor.execute(sql, values)
conn.commit()
conn.close()
return jsonify({'success': True})
@app.route('/api/user/<int:user_id>/conversations/<int:conv_id>', methods=['DELETE'])
def delete_user_conversation(user_id, conv_id):
"""删除对话"""
conn = get_db()
cursor = conn.cursor()
# 验证对话属于该用户
cursor.execute('SELECT id FROM conversations WHERE id = ? AND user_id = ?', (conv_id, user_id))
if not cursor.fetchone():
conn.close()
return jsonify({'error': '对话不存在'}), 404
cursor.execute('DELETE FROM conversations WHERE id = ?', (conv_id,))
conn.commit()
conn.close()
return jsonify({'success': True})
# ==================== 大模型接口管理 ==================== # ==================== 大模型接口管理 ====================
@app.route('/api/admin/llm', methods=['GET']) @app.route('/api/admin/llm', methods=['GET'])

View File

@@ -1620,14 +1620,43 @@ function showEditProfilePage() {
return; return;
} }
// 获取完整用户信息 // 如果用户已登录且有ID从 backend 获取完整用户信息
const users = JSON.parse(localStorage.getItem('registeredUsers') || '[]'); if (currentUser.id) {
const fullUser = users.find(u => u.username === currentUser.username); fetch(`/api/admin/users/${currentUser.id}`)
.then(res => res.json())
.then(data => {
if (data && data.id) {
// 更新 currentUser 的完整信息
currentUser.avatar = data.avatar || '👤';
currentUser.signature = data.signature || '';
currentUser.email = data.email || '';
currentUser.gender = data.gender || '';
currentUser.age = data.age || '';
currentUser.region = data.region || '';
currentUser.phone = data.phone || '';
// 渲染编辑页面
renderEditProfilePage();
} else {
// 使用本地数据
renderEditProfilePage();
}
})
.catch(e => {
// 使用本地数据
renderEditProfilePage();
});
} else {
// 游客用户,使用本地数据
renderEditProfilePage();
}
}
function renderEditProfilePage() {
const avatar = currentUser.avatar || '👤'; const avatar = currentUser.avatar || '👤';
const signature = currentUser.signature || ''; const signature = currentUser.signature || '';
const phone = fullUser?.phone || ''; const phone = currentUser.phone || '';
const email = fullUser?.email || ''; const email = currentUser.email || '';
const gender = currentUser.gender || ''; const gender = currentUser.gender || '';
const age = currentUser.age || ''; const age = currentUser.age || '';
const region = currentUser.region || ''; const region = currentUser.region || '';
@@ -1773,26 +1802,6 @@ function handleSaveProfile() {
return; return;
} }
// 检查用户名是否被其他人占用
const users = JSON.parse(localStorage.getItem('registeredUsers') || '[]');
if (newUsername !== currentUser.username) {
if (users.find(u => u.username === newUsername)) {
showToast('用户名已被占用');
return;
}
// 更新用户名
const userIndex = users.findIndex(u => u.username === currentUser.username);
if (userIndex >= 0) {
users[userIndex].username = newUsername;
}
}
// 更新邮箱
const userIndex = users.findIndex(u => u.username === currentUser.username);
if (userIndex >= 0) {
users[userIndex].email = newEmail;
}
// 更新 currentUser // 更新 currentUser
currentUser.avatar = newAvatar; currentUser.avatar = newAvatar;
currentUser.username = newUsername; currentUser.username = newUsername;
@@ -1800,12 +1809,42 @@ function handleSaveProfile() {
currentUser.gender = newGender; currentUser.gender = newGender;
currentUser.age = newAge ? parseInt(newAge) : ''; currentUser.age = newAge ? parseInt(newAge) : '';
currentUser.region = newRegion; currentUser.region = newRegion;
currentUser.email = newEmail;
saveCurrentUser(); // 如果用户已登录,调用 backend API 更新
localStorage.setItem('registeredUsers', JSON.stringify(users)); if (currentUser.id) {
fetch(`/api/user/${currentUser.id}`, {
showToast('保存成功'); method: 'PUT',
switchPage('profile'); headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username: newUsername,
email: newEmail,
avatar: newAvatar,
signature: newSignature,
gender: newGender,
age: newAge ? parseInt(newAge) : null,
region: newRegion
})
})
.then(res => res.json())
.then(data => {
if (data.success) {
saveCurrentUser();
showToast('保存成功');
switchPage('profile');
} else {
showToast(data.error || '保存失败');
}
})
.catch(e => {
showToast('网络错误,请重试');
});
} else {
// 未登录用户,保存到本地
saveCurrentUser();
showToast('保存成功');
switchPage('profile');
}
} }
// ==================== 消息通知页面 ==================== // ==================== 消息通知页面 ====================
@@ -2466,8 +2505,12 @@ function handleLogin() {
registeredAt: Date.now() registeredAt: Date.now()
}; };
saveCurrentUser(); saveCurrentUser();
showToast('登录成功');
showMainPage(); // 从 backend 加载用户对话数据
loadUserConversations().then(() => {
showToast('登录成功');
showMainPage();
});
} else { } else {
showToast(data.error || '用户名或密码错误'); showToast(data.error || '用户名或密码错误');
} }
@@ -2633,8 +2676,12 @@ function handleRegister() {
registeredAt: Date.now() registeredAt: Date.now()
}; };
saveCurrentUser(); saveCurrentUser();
showToast('注册成功');
showMainPage(); // 加载对话数据(新用户为空)
loadUserConversations().then(() => {
showToast('注册成功');
showMainPage();
});
} else { } else {
showToast(data.error || '注册失败'); showToast(data.error || '注册失败');
} }
@@ -3496,6 +3543,17 @@ function deleteConversation(id) {
conversations = conversations.filter(c => c.id !== id); conversations = conversations.filter(c => c.id !== id);
saveConversations(); saveConversations();
// 如果用户已登录,从 backend 删除
if (currentUser && currentUser.id) {
const convId = parseInt(id);
if (convId > 0) {
fetch(`/api/user/${currentUser.id}/conversations/${convId}`, {
method: 'DELETE'
}).catch(e => console.error('删除对话失败:', e));
}
}
showConversationList(); showConversationList();
} }
@@ -4166,7 +4224,91 @@ function setupScrollListener() {
// 保存对话列表 // 保存对话列表
function saveConversations() { function saveConversations() {
// 保存到本地存储(离线可用)
localStorage.setItem('conversations', JSON.stringify(conversations)); localStorage.setItem('conversations', JSON.stringify(conversations));
// 如果用户已登录,同步到 backend
if (currentUser && currentUser.id) {
syncConversationsToBackend();
}
}
// 同步对话数据到 backend
async function syncConversationsToBackend() {
if (!currentUser || !currentUser.id) return;
try {
// 批量同步所有对话
for (const conv of conversations) {
const convId = parseInt(conv.id);
if (convId > 0) {
// 更新现有对话
await fetch(`/api/user/${currentUser.id}/conversations/${convId}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
title: conv.title,
messages: conv.messages,
agentId: conv.agentId
})
});
} else {
// 新对话,需要创建
const res = await fetch(`/api/user/${currentUser.id}/conversations`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
title: conv.title,
messages: conv.messages,
agentId: conv.agentId
})
});
const data = await res.json();
if (data.success && data.id) {
// 更新本地ID为backend ID
conv.id = data.id;
}
}
}
} catch (e) {
// 同步失败,静默处理(下次登录时会重新加载)
console.error('同步对话失败:', e);
}
}
// 从 backend 加载用户对话数据
async function loadUserConversations() {
if (!currentUser || !currentUser.id) return;
try {
const res = await fetch(`/api/user/${currentUser.id}/conversations`);
const data = await res.json();
if (Array.isArray(data)) {
// 合并本地和云端数据(云端优先)
const localConvIds = conversations.map(c => c.id);
for (const cloudConv of data) {
const localIdx = localConvIds.indexOf(cloudConv.id);
if (localIdx >= 0) {
// 更新本地对话(云端数据优先)
conversations[localIdx] = cloudConv;
} else {
// 添加云端对话
conversations.push(cloudConv);
}
}
// 按更新时间排序
conversations.sort((a, b) => b.updatedAt - a.updatedAt);
// 保存到本地
localStorage.setItem('conversations', JSON.stringify(conversations));
}
} catch (e) {
console.error('加载对话失败:', e);
// 使用本地数据
}
} }
// 显示提示 // 显示提示