对话逻辑修复

This commit is contained in:
2025-07-25 18:04:22 +08:00
parent f576de68da
commit b1f8aa175d
5 changed files with 381 additions and 48 deletions
+1 -1
View File
File diff suppressed because one or more lines are too long
@@ -79,7 +79,6 @@ public class MessageController {
@RequestParam(defaultValue = "20") Long size) { @RequestParam(defaultValue = "20") Long size) {
log.info("获取用户消息分页: current={}, size={}", current, size); log.info("获取用户消息分页: current={}, size={}", current, size);
try {
// 构建请求对象 // 构建请求对象
MessagePageRequest request = new MessagePageRequest(); MessagePageRequest request = new MessagePageRequest();
request.setCurrent(current); request.setCurrent(current);
@@ -88,14 +87,6 @@ public class MessageController {
PageResult<MessageResponse> pageResult = messageService.getUserMessagesWithPage(request); PageResult<MessageResponse> pageResult = messageService.getUserMessagesWithPage(request);
log.info("获取用户消息分页成功: total={}", pageResult.getTotal()); log.info("获取用户消息分页成功: total={}", pageResult.getTotal());
return Result.success(pageResult); return Result.success(pageResult);
} catch (IllegalStateException e) {
log.error("用户未认证: {}", e.getMessage());
return Result.error(401, "用户未登录或认证失败");
} catch (Exception e) {
log.error("获取用户消息失败", e);
return Result.error(500, "获取消息失败,请稍后重试");
}
} }
/** /**
+191
View File
@@ -0,0 +1,191 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>消息历史API测试</title>
<style>
body { font-family: Arial, sans-serif; max-width: 1000px; margin: 0 auto; padding: 20px; }
.test-section { margin: 20px 0; padding: 15px; border: 1px solid #ddd; border-radius: 5px; }
.test-button { background: #1890ff; color: white; border: none; padding: 8px 16px; border-radius: 4px; cursor: pointer; margin: 5px; }
.result { background: #f5f5f5; padding: 10px; border-radius: 4px; margin-top: 10px; white-space: pre-wrap; font-family: monospace; max-height: 400px; overflow-y: auto; }
.error { background: #fff2f0; border: 1px solid #ffccc7; color: #ff4d4f; }
.success { background: #f6ffed; border: 1px solid #b7eb8f; color: #52c41a; }
input { padding: 5px; margin: 5px; width: 300px; }
</style>
</head>
<body>
<h1>消息历史API测试</h1>
<div class="test-section">
<h3>Token设置</h3>
<input type="text" id="tokenInput" placeholder="请输入JWT Token">
<button class="test-button" onclick="loadToken()">从localStorage加载</button>
<button class="test-button" onclick="saveToken()">保存到localStorage</button>
</div>
<div class="test-section">
<h3>API测试</h3>
<button class="test-button" onclick="testGetUserMessages()">测试分页查询</button>
<button class="test-button" onclick="testSearchMessages()">测试搜索消息</button>
<button class="test-button" onclick="testRecentMessages()">测试最近消息</button>
<button class="test-button" onclick="testAllAPIs()">测试所有API</button>
</div>
<div class="test-section">
<h3>测试结果</h3>
<div id="result" class="result">等待测试...</div>
</div>
<script>
function loadToken() {
const token = localStorage.getItem('token');
if (token) {
document.getElementById('tokenInput').value = token;
showResult('Token已加载', 'success');
} else {
showResult('localStorage中没有token', 'error');
}
}
function saveToken() {
const token = document.getElementById('tokenInput').value.trim();
if (token) {
localStorage.setItem('token', token);
showResult('Token已保存到localStorage', 'success');
} else {
showResult('请先输入Token', 'error');
}
}
function showResult(message, type = 'success') {
const resultDiv = document.getElementById('result');
resultDiv.textContent = message;
resultDiv.className = `result ${type}`;
}
async function makeRequest(url, options = {}) {
const token = document.getElementById('tokenInput').value.trim();
if (!token) {
throw new Error('请先输入Token');
}
const headers = {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
...options.headers
};
const response = await fetch(url, { ...options, headers });
const data = await response.json();
return { status: response.status, data };
}
async function testGetUserMessages() {
try {
showResult('正在测试分页查询...', 'success');
const result = await makeRequest('/api/message/user/page?current=1&size=5');
const message = `分页查询结果:
状态码: ${result.status}
响应: ${JSON.stringify(result.data, null, 2)}`;
showResult(message, result.status === 200 ? 'success' : 'error');
} catch (error) {
showResult(`分页查询错误: ${error.message}`, 'error');
}
}
async function testSearchMessages() {
try {
showResult('正在测试搜索消息...', 'success');
const result = await makeRequest('/api/message/user/search', {
method: 'POST',
body: JSON.stringify({ keyword: '测试', limit: 5 })
});
const message = `搜索消息结果:
状态码: ${result.status}
响应: ${JSON.stringify(result.data, null, 2)}`;
showResult(message, result.status === 200 ? 'success' : 'error');
} catch (error) {
showResult(`搜索消息错误: ${error.message}`, 'error');
}
}
async function testRecentMessages() {
try {
showResult('正在测试最近消息...', 'success');
const result = await makeRequest('/api/message/user/recent', {
method: 'POST',
body: JSON.stringify({ limit: 5 })
});
const message = `最近消息结果:
状态码: ${result.status}
响应: ${JSON.stringify(result.data, null, 2)}`;
showResult(message, result.status === 200 ? 'success' : 'error');
} catch (error) {
showResult(`最近消息错误: ${error.message}`, 'error');
}
}
async function testAllAPIs() {
try {
showResult('正在测试所有API...', 'success');
const results = [];
// 测试分页查询
try {
const pageResult = await makeRequest('/api/message/user/page?current=1&size=5');
results.push(`✅ 分页查询: ${pageResult.status} - ${pageResult.data.message || 'OK'}`);
} catch (error) {
results.push(`❌ 分页查询: ${error.message}`);
}
// 测试搜索
try {
const searchResult = await makeRequest('/api/message/user/search', {
method: 'POST',
body: JSON.stringify({ keyword: '测试', limit: 5 })
});
results.push(`✅ 搜索消息: ${searchResult.status} - ${searchResult.data.message || 'OK'}`);
} catch (error) {
results.push(`❌ 搜索消息: ${error.message}`);
}
// 测试最近消息
try {
const recentResult = await makeRequest('/api/message/user/recent', {
method: 'POST',
body: JSON.stringify({ limit: 5 })
});
results.push(`✅ 最近消息: ${recentResult.status} - ${recentResult.data.message || 'OK'}`);
} catch (error) {
results.push(`❌ 最近消息: ${error.message}`);
}
const message = `所有API测试结果:
${results.join('\n')}
详细信息请查看浏览器控制台`;
showResult(message, 'success');
} catch (error) {
showResult(`测试过程中发生错误: ${error.message}`, 'error');
}
}
// 页面加载时自动加载token
window.onload = loadToken;
</script>
</body>
</html>
+6 -13
View File
@@ -173,14 +173,14 @@ export const useChatStore = defineStore('chat', () => {
return segments return segments
} }
// 添加AI回复消息(支持分段显示 // 添加AI回复消息(直接显示完整内容
const addAiReplyMessages = (content: string, delay: number = 1000) => { const addAiReplyMessages = (content: string) => {
const segments = splitAiReply(content) // 停止输入状态
isTyping.value = false
segments.forEach((segment, index) => { // 直接添加完整的AI回复
setTimeout(() => {
const aiMessage = addMessage({ const aiMessage = addMessage({
content: segment.trim(), content: content.trim(),
type: 'ai', type: 'ai',
sessionId: currentSession.value?.id sessionId: currentSession.value?.id
}) })
@@ -188,13 +188,6 @@ export const useChatStore = defineStore('chat', () => {
// 强制触发响应式更新 // 强制触发响应式更新
console.log('AI消息已添加,当前消息总数:', messages.value.length) console.log('AI消息已添加,当前消息总数:', messages.value.length)
console.log('最新AI消息:', aiMessage) console.log('最新AI消息:', aiMessage)
// 最后一条消息后停止输入状态
if (index === segments.length - 1) {
isTyping.value = false
}
}, index * delay)
})
} }
// WebSocket消息处理 // WebSocket消息处理
+168 -10
View File
@@ -31,7 +31,10 @@
</div> </div>
<div class="header-right"> <div class="header-right">
<a-button type="text" @click="showHistory = true" class="action-btn"> <a-button type="text" @click="testAPI" class="action-btn" style="margin-right: 8px;">
测试API
</a-button>
<a-button type="text" @click="openHistoryDrawer" class="action-btn">
<HistoryOutlined /> <HistoryOutlined />
</a-button> </a-button>
<a-button <a-button
@@ -147,7 +150,7 @@
placement="right" placement="right"
:width="400" :width="400"
class="history-drawer" class="history-drawer"
@open="loadHistoryMessages(1)" @open="onHistoryDrawerOpen"
> >
<div class="history-content"> <div class="history-content">
<div class="search-section"> <div class="search-section">
@@ -344,7 +347,7 @@
messageInput.value = '' messageInput.value = ''
await chatStore.sendMessage(content) await chatStore.sendMessage(content)
scrollToBottom() forceScrollToBottom()
} }
// 获取连接状态文本 // 获取连接状态文本
@@ -374,14 +377,55 @@
return '和开开说点什么...' return '和开开说点什么...'
} }
const scrollToBottom = () => { const scrollToBottom = (smooth = true) => {
nextTick(() => { nextTick(() => {
if (chatMainRef.value) { if (chatMainRef.value) {
if (smooth) {
// 平滑滚动到底部
chatMainRef.value.scrollTo({
top: chatMainRef.value.scrollHeight,
behavior: 'smooth'
})
} else {
// 立即滚动到底部
chatMainRef.value.scrollTop = chatMainRef.value.scrollHeight chatMainRef.value.scrollTop = chatMainRef.value.scrollHeight
} }
}
}) })
} }
// 检查用户是否在聊天底部
const isUserAtBottom = () => {
if (!chatMainRef.value) return true
const { scrollTop, scrollHeight, clientHeight } = chatMainRef.value
// 允许一些误差(50px),认为用户在底部
return scrollHeight - scrollTop - clientHeight < 50
}
// 智能滚动到底部(只有用户在底部时才滚动)
const smartScrollToBottom = () => {
if (isUserAtBottom()) {
scrollToBottom(true)
}
}
// 强制滚动到底部(用于发送消息等场景)
const forceScrollToBottom = () => {
// 立即滚动
scrollToBottom(false)
// 延迟滚动,确保内容完全渲染
setTimeout(() => {
scrollToBottom(true)
}, 100)
// 再次延迟滚动,处理可能的异步内容
setTimeout(() => {
scrollToBottom(true)
}, 500)
}
// 生成情绪记录总结 // 生成情绪记录总结
const generateEmotionSummary = async () => { const generateEmotionSummary = async () => {
if (emotionSummaryLoading.value) return if (emotionSummaryLoading.value) return
@@ -437,9 +481,64 @@
}) })
} }
// 打开历史记录抽屉
const openHistoryDrawer = async () => {
console.log('点击聊天记录按钮')
showHistory.value = true
// 确保抽屉打开后再加载数据
await nextTick()
await loadHistoryMessages(1)
}
// 抽屉打开事件
const onHistoryDrawerOpen = () => {
console.log('聊天记录抽屉已打开,开始加载历史记录')
loadHistoryMessages(1)
}
// 测试API调用
const testAPI = async () => {
console.log('=== 开始测试API ===')
try {
// 测试原始API调用
console.log('1. 测试原始axios调用...')
const token = localStorage.getItem('token')
console.log('Token:', token ? `${token.substring(0, 20)}...` : 'null')
const response = await fetch('/api/message/user/page?current=1&size=5', {
method: 'GET',
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json'
}
})
console.log('原始响应状态:', response.status)
const rawData = await response.json()
console.log('原始响应数据:', rawData)
// 测试封装的API调用
console.log('2. 测试封装的API调用...')
const apiData = await messageApi.getUserMessages(1, 5)
console.log('封装API返回数据:', apiData)
} catch (error) {
console.error('API测试失败:', error)
}
console.log('=== API测试完成 ===')
}
// 加载历史记录 // 加载历史记录
const loadHistoryMessages = async (page = 1) => { const loadHistoryMessages = async (page = 1) => {
if (historyLoading.value) return console.log('loadHistoryMessages 被调用,page:', page)
if (historyLoading.value) {
console.log('已经在加载中,跳过')
return
}
try { try {
historyLoading.value = true historyLoading.value = true
@@ -448,13 +547,17 @@
page, page,
pageSize: historyPagination.value.pageSize, pageSize: historyPagination.value.pageSize,
token: !!localStorage.getItem('token'), token: !!localStorage.getItem('token'),
userInfo: userStore.userInfo userInfo: userStore.userInfo,
apiUrl: `/message/user/page?current=${page}&size=${historyPagination.value.pageSize}`
}) })
// 调用API获取用户消息(后端会从token中获取用户信息) // 调用API获取用户消息(后端会从token中获取用户信息)
console.log('正在调用 messageApi.getUserMessages...')
const pageData = await messageApi.getUserMessages(page, historyPagination.value.pageSize) const pageData = await messageApi.getUserMessages(page, historyPagination.value.pageSize)
console.log('API返回数据:', pageData) console.log('API调用成功,返回数据:', pageData)
console.log('API返回数据类型:', typeof pageData)
console.log('API返回数据结构:', Object.keys(pageData || {}))
if (page === 1) { if (page === 1) {
historyMessages.value = pageData.records || [] historyMessages.value = pageData.records || []
@@ -475,14 +578,24 @@
} catch (error) { } catch (error) {
console.error('加载历史记录时发生错误:', error) console.error('加载历史记录时发生错误:', error)
console.error('错误详情:', {
message: error.message,
response: error.response,
status: error.response?.status,
data: error.response?.data
})
// 显示用户友好的错误信息 // 显示用户友好的错误信息
if (error.response?.status === 401) { if (error.response?.status === 401) {
console.log('认证失败,可能需要重新登录') console.log('认证失败,可能需要重新登录')
// 可以在这里添加跳转到登录页的逻辑
} else if (error.response?.status === 500) { } else if (error.response?.status === 500) {
console.log('服务器错误,请稍后重试') console.log('服务器错误,请稍后重试')
} else {
console.log('未知错误:', error.message)
} }
} finally { } finally {
console.log('加载历史记录完成,设置 loading 为 false')
historyLoading.value = false historyLoading.value = false
} }
} }
@@ -523,11 +636,36 @@
} }
} }
// 监听消息变化,自动滚动到底部 // 监听消息变化,智能滚动到底部
watch( watch(
() => chatStore.messages.length, () => chatStore.messages.length,
(newLength, oldLength) => {
// 如果有新消息添加
if (newLength > oldLength) {
const lastMessage = chatStore.messages[chatStore.messages.length - 1]
// 如果是用户发送的消息,强制滚动
if (lastMessage?.type === 'user') {
forceScrollToBottom()
} else {
// 如果是AI回复,智能滚动(只有用户在底部时才滚动)
smartScrollToBottom()
}
}
}
)
// 监听最后一条消息的内容变化(处理AI回复的实时更新)
watch(
() => { () => {
scrollToBottom() const messages = chatStore.messages
return messages.length > 0 ? messages[messages.length - 1]?.content : ''
},
(newContent, oldContent) => {
// 如果最后一条消息内容发生变化(AI回复更新),智能滚动
if (newContent !== oldContent && newContent) {
smartScrollToBottom()
}
} }
) )
@@ -567,7 +705,7 @@
// 加载最近的聊天记录 // 加载最近的聊天记录
await loadRecentMessages() await loadRecentMessages()
scrollToBottom() forceScrollToBottom()
}) })
// 组件卸载 // 组件卸载
@@ -720,6 +858,26 @@
flex: 1; flex: 1;
overflow-y: auto; overflow-y: auto;
padding: $spacing-lg; padding: $spacing-lg;
scroll-behavior: smooth; // 启用平滑滚动
// 确保滚动容器正确工作
&::-webkit-scrollbar {
width: 6px;
}
&::-webkit-scrollbar-track {
background: rgba(0, 0, 0, 0.1);
border-radius: 3px;
}
&::-webkit-scrollbar-thumb {
background: rgba(0, 0, 0, 0.3);
border-radius: 3px;
&:hover {
background: rgba(0, 0, 0, 0.5);
}
}
} }
.messages-container { .messages-container {