AI聊天更新历史记录
This commit is contained in:
7
.trae/TODO.md
Normal file
7
.trae/TODO.md
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
# TODO:
|
||||||
|
|
||||||
|
- [x] 1: 分析控制台输出,确定API返回数据的正确结构 (priority: High)
|
||||||
|
- [x] 2: 修复initChat方法中AI消息内容的数据映射 (priority: High)
|
||||||
|
- [x] 3: 修复loadMoreHistory方法中的数据映射 (priority: High)
|
||||||
|
- [x] 4: 移除调试代码,恢复正常UI显示 (priority: Medium)
|
||||||
|
- [x] 5: 测试修复后的AI消息显示功能 (priority: Medium)
|
||||||
@@ -1,38 +1,78 @@
|
|||||||
// src/api/index.js
|
// src/api/ai_index.js
|
||||||
// import request from '@/utils/ai_request.js'
|
// import request from '@/utils/ai_request.js'
|
||||||
import request from "../../utils/ai_request";
|
import request from "@/utils/ai_request.js";
|
||||||
|
|
||||||
// 获取历史
|
// 获取历史
|
||||||
export const getHistory = ({
|
export const getHistory = ({
|
||||||
conversationId,
|
conversationId,
|
||||||
user,
|
user,
|
||||||
limit = 20
|
limit = 20,
|
||||||
|
beforeId
|
||||||
}) => {
|
}) => {
|
||||||
|
const params = {
|
||||||
|
conversationId,
|
||||||
|
user,
|
||||||
|
limit
|
||||||
|
};
|
||||||
|
|
||||||
|
// 如果有beforeId参数,添加到请求中
|
||||||
|
if (beforeId) {
|
||||||
|
params.beforeId = beforeId;
|
||||||
|
}
|
||||||
|
|
||||||
return request({
|
return request({
|
||||||
url: '/aitutor/aichat/getMessagesToUser',
|
url: '/aitutor/aichat/getMessagesToUser',
|
||||||
method: 'get',
|
method: 'get',
|
||||||
params: {
|
params
|
||||||
conversationId,
|
|
||||||
user,
|
|
||||||
limit
|
|
||||||
}
|
|
||||||
// headers: {
|
|
||||||
// Authorization: 'Bearer eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6IjBmMTY3NmY2LTgwOGMtNGUwMC04NDJjLWIwNmY1ZTM5NzJlNCJ9.VVc6OwQ-Xn9pxzYbPhlCpvDp6TwESS00gJi9IXUEIbFw4RFACZDmYCYjQ7voTM4fppy9SAMJCWT-L7Uy-K1eqw'
|
|
||||||
// }
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// export const getHistory = ({
|
||||||
|
// conversationId,
|
||||||
|
// user,
|
||||||
|
// limit = 20
|
||||||
|
// }) => {
|
||||||
|
// return request({
|
||||||
|
// url: '/aitutor/aichat/getMessagesToUser',
|
||||||
|
// method: 'get',
|
||||||
|
// params: {
|
||||||
|
// conversationId,
|
||||||
|
// user,
|
||||||
|
// limit
|
||||||
|
// }
|
||||||
|
// // headers: {
|
||||||
|
// // Authorization: 'Bearer eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6IjBmMTY3NmY2LTgwOGMtNGUwMC04NDJjLWIwNmY1ZTM5NzJlNCJ9.VVc6OwQ-Xn9pxzYbPhlCpvDp6TwESS00gJi9IXUEIbFw4RFACZDmYCYjQ7voTM4fppy9SAMJCWT-L7Uy-K1eqw'
|
||||||
|
// // }
|
||||||
|
// });
|
||||||
|
// };
|
||||||
|
|
||||||
// 点赞/点踩 action: 1 点赞 0 点踩
|
// 点赞/点踩 action: 1 点赞 0 点踩
|
||||||
export const sendFeedback = ({
|
export const sendFeedback = ({
|
||||||
messageId,
|
messageId,
|
||||||
action
|
action,
|
||||||
|
user
|
||||||
}) => {
|
}) => {
|
||||||
return request({
|
return request({
|
||||||
url: '/api/chat/feedback',
|
url: '/aitutor/aichat/feedback',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data: {
|
data: {
|
||||||
messageId,
|
message_id: messageId,
|
||||||
action
|
rating: action === 1 ? 'like' : 'dislike', // 添加rating参数
|
||||||
|
user
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
// export const sendFeedback = ({
|
||||||
|
// messageId,
|
||||||
|
// action
|
||||||
|
// }) => {
|
||||||
|
// return request({
|
||||||
|
// url: '/api/chat/feedback',
|
||||||
|
// method: 'post',
|
||||||
|
// data: {
|
||||||
|
// messageId,
|
||||||
|
// action
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// };
|
||||||
@@ -50,362 +50,350 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {
|
import {
|
||||||
getHistory
|
getHistory
|
||||||
} from '../../api/aiChat/ai_index.js'; // 历史记录API
|
} from '../../api/aiChat/ai_index.js';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'HistoryDrawer',
|
name: 'HistoryDrawer',
|
||||||
props: {
|
props: {
|
||||||
visible: Boolean // 控制抽屉显示
|
visible: Boolean
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
historyRecords: [],
|
||||||
|
filteredRecords: [],
|
||||||
|
searchKeyword: '', // 移除了重复定义
|
||||||
|
loading: false // 新增loading状态
|
||||||
|
};
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
visible(newVal) {
|
||||||
|
if (newVal) this.loadHistoryRecords();
|
||||||
|
else this.clearSearch();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
closeDrawer() {
|
||||||
|
this.$emit('close');
|
||||||
},
|
},
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
historyRecords: [], // 原始历史记录
|
|
||||||
filteredRecords: [], // 过滤后的历史记录
|
|
||||||
searchKeyword: '' // 搜索关键词
|
|
||||||
};
|
|
||||||
},
|
|
||||||
watch: {
|
|
||||||
// 监听visible变化,显示时加载记录
|
|
||||||
visible(newVal) {
|
|
||||||
if (newVal) this.loadHistoryRecords();
|
|
||||||
else this.clearSearch();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
// 关闭抽屉
|
|
||||||
closeDrawer() {
|
|
||||||
this.$emit('close');
|
|
||||||
},
|
|
||||||
|
|
||||||
/* ========== 1. 读本地缓存兜底 ========== */
|
async loadHistoryRecords() {
|
||||||
renderLocal(list) {
|
this.loading = true;
|
||||||
// 将本地缓存的消息按日期分组
|
try {
|
||||||
|
// 1. 获取当前用户学号
|
||||||
|
const userNo = uni.getStorageSync('stuNo');
|
||||||
|
if (!userNo) throw new Error('未获取到用户学号');
|
||||||
|
|
||||||
|
// 2. 调用接口获取数据
|
||||||
|
const res = await getHistory({
|
||||||
|
user: userNo,
|
||||||
|
conversationId: '',
|
||||||
|
limit: 20
|
||||||
|
});
|
||||||
|
console.log('接口响应:', res);
|
||||||
|
|
||||||
|
// 3. 处理响应数据 - 修正数据结构解析
|
||||||
|
const list = Array.isArray(res.data?.data) ? res.data.data : [];
|
||||||
|
console.log('解析后的数据列表:', list);
|
||||||
|
|
||||||
|
// 4. 分组处理
|
||||||
const groupMap = {};
|
const groupMap = {};
|
||||||
list.forEach((m, idx) => {
|
list.forEach(item => {
|
||||||
const date = new Date();
|
// 修正字段映射
|
||||||
const formatted = {
|
const timestamp = item.created_at || item.create_time || item.timestamp || Date.now() /
|
||||||
|
1000;
|
||||||
|
const date = new Date(timestamp * 1000);
|
||||||
|
|
||||||
|
const record = {
|
||||||
|
id: item.id || Math.random().toString(36).slice(2),
|
||||||
date: this.formatDate(date),
|
date: this.formatDate(date),
|
||||||
time: this.formatTime(date),
|
time: this.formatTime(date),
|
||||||
content: m.sender === 'user' ? m.content : '',
|
content: item.query || item.content || '未知内容',
|
||||||
reply: m.sender === 'ai' ? m.content : '',
|
reply: item.answer || item.reply || '暂无回复',
|
||||||
id: idx
|
timestamp: timestamp
|
||||||
};
|
};
|
||||||
|
|
||||||
const title = this.getGroupTitle(date);
|
const title = this.getGroupTitle(date);
|
||||||
(groupMap[title] ||= []).push(formatted);
|
(groupMap[title] ||= []).push(record);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 转换为数组形式
|
// 5. 排序并更新数据
|
||||||
this.historyRecords = Object.entries(groupMap).map(([t, arr]) => ({
|
this.historyRecords = Object.entries(groupMap).map(([title, arr]) => ({
|
||||||
title: t,
|
title,
|
||||||
list: arr
|
list: arr.sort((a, b) => b.timestamp - a.timestamp)
|
||||||
}));
|
}));
|
||||||
|
|
||||||
this.filteredRecords = [...this.historyRecords];
|
this.filteredRecords = [...this.historyRecords];
|
||||||
},
|
console.log('最终处理的历史记录:', this.historyRecords);
|
||||||
|
} catch (e) {
|
||||||
/* ========== 2. 加载接口或本地缓存 ========== */
|
console.error('加载失败:', e);
|
||||||
async loadHistoryRecords() {
|
uni.showToast({
|
||||||
try {
|
title: `加载失败: ${e.message}`,
|
||||||
// 1. 先读本地缓存,立即显示(避免空白)
|
icon: 'none',
|
||||||
const local = uni.getStorageSync('chatHistory') || [];
|
duration: 3000
|
||||||
if (local.length) this.renderLocal(local);
|
|
||||||
|
|
||||||
// 2. 再调接口,成功后覆盖本地
|
|
||||||
const res = await getHistory({
|
|
||||||
conversationId: '5665af64-22b4-4a59-b15f-2126eb056302',
|
|
||||||
user: '2023429112',
|
|
||||||
limit: 50
|
|
||||||
});
|
|
||||||
console.log('原始返回结构', res);
|
|
||||||
|
|
||||||
const list = res?.data?.data || [];
|
|
||||||
if (!list.length) return; // 接口返回空数组也保留本地
|
|
||||||
|
|
||||||
// 处理API返回的数据
|
|
||||||
const groupMap = {};
|
|
||||||
list.forEach(item => {
|
|
||||||
const ts = item.created_at ?? item.create_time ?? item.timestamp ?? 0;
|
|
||||||
const date = String(ts).length === 13 ? new Date(ts) : new Date(ts * 1000);
|
|
||||||
|
|
||||||
const formatted = {
|
|
||||||
date: this.formatDate(date),
|
|
||||||
time: this.formatTime(date),
|
|
||||||
content: item.query || item.question || item.content || '未知内容',
|
|
||||||
reply: item.answer || item.reply || '暂无回复',
|
|
||||||
id: item.id || Math.random().toString(36).slice(2)
|
|
||||||
};
|
|
||||||
|
|
||||||
const title = this.getGroupTitle(date);
|
|
||||||
(groupMap[title] ||= []).push(formatted);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 按时间排序并更新数据
|
|
||||||
this.historyRecords = Object.entries(groupMap).map(([title, arr]) => ({
|
|
||||||
title,
|
|
||||||
list: arr.sort((a, b) => b.timestamp - a.timestamp)
|
|
||||||
}));
|
|
||||||
this.filteredRecords = [...this.historyRecords];
|
|
||||||
} catch (e) {
|
|
||||||
console.error('历史接口 401,已使用本地缓存兜底', e);
|
|
||||||
// 接口失败时,保留本地缓存(不覆盖)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
// 处理搜索输入
|
|
||||||
handleSearch() {
|
|
||||||
const kw = this.searchKeyword.trim().toLowerCase();
|
|
||||||
if (!kw) return (this.filteredRecords = [...this.historyRecords]);
|
|
||||||
|
|
||||||
// 过滤历史记录
|
|
||||||
this.filteredRecords = this.historyRecords
|
|
||||||
.map(g => ({
|
|
||||||
...g,
|
|
||||||
list: g.list.filter(i =>
|
|
||||||
i.content.toLowerCase().includes(kw) ||
|
|
||||||
i.reply.toLowerCase().includes(kw)
|
|
||||||
)
|
|
||||||
}))
|
|
||||||
.filter(g => g.list.length);
|
|
||||||
},
|
|
||||||
|
|
||||||
// 清除搜索
|
|
||||||
clearSearch() {
|
|
||||||
this.searchKeyword = '';
|
|
||||||
this.filteredRecords = [...this.historyRecords];
|
|
||||||
},
|
|
||||||
|
|
||||||
// 高亮搜索关键词
|
|
||||||
highlightKeyword(text) {
|
|
||||||
if (!this.searchKeyword || !text) return text;
|
|
||||||
const kw = this.searchKeyword.trim();
|
|
||||||
return text.replace(
|
|
||||||
new RegExp(kw, 'gi'),
|
|
||||||
`<span class="highlight">${kw}</span>`
|
|
||||||
);
|
|
||||||
},
|
|
||||||
|
|
||||||
// 获取分组标题(今天/昨天/7天内等)
|
|
||||||
getGroupTitle(date) {
|
|
||||||
const now = new Date();
|
|
||||||
const today = new Date(now.setHours(0, 0, 0, 0));
|
|
||||||
const yesterday = new Date(today).setDate(today.getDate() - 1);
|
|
||||||
const week = new Date(today).setDate(today.getDate() - 7);
|
|
||||||
const month = new Date(today).setDate(today.getDate() - 30);
|
|
||||||
|
|
||||||
if (date >= today) return '今天';
|
|
||||||
if (date >= yesterday) return '昨天';
|
|
||||||
if (date >= week) return '7天内';
|
|
||||||
if (date >= month) return '30天内';
|
|
||||||
return '更早';
|
|
||||||
},
|
|
||||||
|
|
||||||
// 格式化日期为YYYY-MM-DD
|
|
||||||
formatDate(date) {
|
|
||||||
return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date
|
|
||||||
.getDate()
|
|
||||||
.toString()
|
|
||||||
.padStart(2, '0')}`;
|
|
||||||
},
|
|
||||||
|
|
||||||
// 格式化时间为HH:MM
|
|
||||||
formatTime(date) {
|
|
||||||
return `${date.getHours().toString().padStart(2, '0')}:${date
|
|
||||||
.getMinutes()
|
|
||||||
.toString()
|
|
||||||
.padStart(2, '0')}`;
|
|
||||||
},
|
|
||||||
|
|
||||||
// 点击历史记录项
|
|
||||||
onItemClick(item) {
|
|
||||||
// 移除HTML标签后触发事件
|
|
||||||
this.$emit('item-click', {
|
|
||||||
...item,
|
|
||||||
content: item.content.replace(/<[^>]+>/g, ''),
|
|
||||||
reply: item.reply.replace(/<[^>]+>/g, '')
|
|
||||||
});
|
});
|
||||||
this.closeDrawer();
|
this.historyRecords = [];
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
// 处理搜索输入
|
||||||
|
handleSearch() {
|
||||||
|
const kw = this.searchKeyword.trim().toLowerCase();
|
||||||
|
if (!kw) return (this.filteredRecords = [...this.historyRecords]);
|
||||||
|
|
||||||
|
// 过滤历史记录
|
||||||
|
this.filteredRecords = this.historyRecords
|
||||||
|
.map(g => ({
|
||||||
|
...g,
|
||||||
|
list: g.list.filter(i =>
|
||||||
|
i.content.toLowerCase().includes(kw) ||
|
||||||
|
i.reply.toLowerCase().includes(kw)
|
||||||
|
)
|
||||||
|
}))
|
||||||
|
.filter(g => g.list.length);
|
||||||
|
},
|
||||||
|
|
||||||
|
// 清除搜索
|
||||||
|
clearSearch() {
|
||||||
|
this.searchKeyword = '';
|
||||||
|
this.filteredRecords = [...this.historyRecords];
|
||||||
|
},
|
||||||
|
|
||||||
|
// 高亮搜索关键词
|
||||||
|
highlightKeyword(text) {
|
||||||
|
if (!this.searchKeyword || !text) return text;
|
||||||
|
const kw = this.searchKeyword.trim();
|
||||||
|
return text.replace(
|
||||||
|
new RegExp(kw, 'gi'),
|
||||||
|
`<span class="highlight">${kw}</span>`
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取分组标题(今天/昨天/7天内等)
|
||||||
|
getGroupTitle(date) {
|
||||||
|
const now = new Date();
|
||||||
|
const today = new Date(now.setHours(0, 0, 0, 0));
|
||||||
|
const yesterday = new Date(today).setDate(today.getDate() - 1);
|
||||||
|
const week = new Date(today).setDate(today.getDate() - 7);
|
||||||
|
const month = new Date(today).setDate(today.getDate() - 30);
|
||||||
|
|
||||||
|
if (date >= today) return '今天';
|
||||||
|
if (date >= yesterday) return '昨天';
|
||||||
|
if (date >= week) return '7天内';
|
||||||
|
if (date >= month) return '30天内';
|
||||||
|
return '更早';
|
||||||
|
},
|
||||||
|
|
||||||
|
// 格式化日期为YYYY-MM-DD
|
||||||
|
formatDate(date) {
|
||||||
|
return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date
|
||||||
|
.getDate()
|
||||||
|
.toString()
|
||||||
|
.padStart(2, '0')}`;
|
||||||
|
},
|
||||||
|
|
||||||
|
// 格式化时间为HH:MM
|
||||||
|
formatTime(date) {
|
||||||
|
return `${date.getHours().toString().padStart(2, '0')}:${date
|
||||||
|
.getMinutes()
|
||||||
|
.toString()
|
||||||
|
.padStart(2, '0')}`;
|
||||||
|
},
|
||||||
|
|
||||||
|
// 点击历史记录项
|
||||||
|
onItemClick(item) {
|
||||||
|
// 移除HTML标签后触发事件
|
||||||
|
this.$emit('item-click', {
|
||||||
|
...item,
|
||||||
|
content: item.content.replace(/<[^>]+>/g, ''),
|
||||||
|
reply: item.reply.replace(/<[^>]+>/g, '')
|
||||||
|
});
|
||||||
|
this.closeDrawer();
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
/* 抽屉容器样式 */
|
/* 抽屉容器样式 */
|
||||||
.drawer-container {
|
.drawer-container {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
z-index: 999;
|
z-index: 999;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 遮罩层样式 */
|
/* 遮罩层样式 */
|
||||||
.drawer-mask {
|
.drawer-mask {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: rgba(0, 0, 0, 0.4);
|
background-color: rgba(0, 0, 0, 0.4);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 抽屉内容区域样式 */
|
/* 抽屉内容区域样式 */
|
||||||
.drawer-content {
|
.drawer-content {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 0;
|
top: 0;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 66.67%;
|
width: 66.67%;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
box-shadow: 2px 0 10px rgba(0, 0, 0, 0.1);
|
box-shadow: 2px 0 10px rgba(0, 0, 0, 0.1);
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 头部样式 */
|
/* 头部样式 */
|
||||||
.drawer-header {
|
.drawer-header {
|
||||||
padding: 15px 15px 10px;
|
padding: 15px 15px 10px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
border-bottom: 1px solid #eee;
|
border-bottom: 1px solid #eee;
|
||||||
height: 50px;
|
height: 50px;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title {
|
.title {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: #333;
|
color: #333;
|
||||||
}
|
}
|
||||||
|
|
||||||
.close-icon {
|
.close-icon {
|
||||||
width: 24px;
|
width: 24px;
|
||||||
height: 24px;
|
height: 24px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 搜索栏样式 */
|
/* 搜索栏样式 */
|
||||||
.search-bar {
|
.search-bar {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 10px 15px;
|
padding: 10px 15px;
|
||||||
background-color: #f5f5f5;
|
background-color: #f5f5f5;
|
||||||
border-bottom: 1px solid #eee;
|
border-bottom: 1px solid #eee;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-icon {
|
.search-icon {
|
||||||
width: 18px;
|
width: 18px;
|
||||||
height: 18px;
|
height: 18px;
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
tint-color: #999;
|
tint-color: #999;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-input {
|
.search-input {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
height: 36px;
|
height: 36px;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border-radius: 18px;
|
border-radius: 18px;
|
||||||
padding: 0 15px;
|
padding: 0 15px;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.clear-icon {
|
.clear-icon {
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 16px;
|
height: 16px;
|
||||||
margin-left: 8px;
|
margin-left: 8px;
|
||||||
tint-color: #999;
|
tint-color: #999;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 历史列表样式 */
|
/* 历史列表样式 */
|
||||||
.history-list {
|
.history-list {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
padding: 10px 0;
|
padding: 10px 0;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 分组标题样式 */
|
/* 分组标题样式 */
|
||||||
.group-title {
|
.group-title {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #888;
|
color: #888;
|
||||||
padding: 12px 20px;
|
padding: 12px 20px;
|
||||||
background-color: #f5f5f5;
|
background-color: #f5f5f5;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 历史记录项样式 */
|
/* 历史记录项样式 */
|
||||||
.history-item {
|
.history-item {
|
||||||
padding: 15px 20px;
|
padding: 15px 20px;
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border-bottom: 1px solid #f0f0f0;
|
border-bottom: 1px solid #f0f0f0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 日期时间样式 */
|
/* 日期时间样式 */
|
||||||
.datetime {
|
.datetime {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.date {
|
.date {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: #666;
|
color: #666;
|
||||||
}
|
}
|
||||||
|
|
||||||
.time {
|
.time {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #999;
|
color: #999;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 消息内容样式 */
|
/* 消息内容样式 */
|
||||||
.record {
|
.record {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
line-height: 1.6;
|
line-height: 1.6;
|
||||||
}
|
}
|
||||||
|
|
||||||
.user-msg {
|
.user-msg {
|
||||||
display: block;
|
display: block;
|
||||||
color: #333;
|
color: #333;
|
||||||
margin-bottom: 6px;
|
margin-bottom: 6px;
|
||||||
padding: 6px 10px;
|
padding: 6px 10px;
|
||||||
background-color: #f9f9f9;
|
background-color: #f9f9f9;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ai-msg {
|
.ai-msg {
|
||||||
display: block;
|
display: block;
|
||||||
color: #333;
|
color: #333;
|
||||||
padding: 6px 10px;
|
padding: 6px 10px;
|
||||||
background-color: #eef7ff;
|
background-color: #eef7ff;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 关键词高亮样式 */
|
/* 关键词高亮样式 */
|
||||||
.highlight {
|
.highlight {
|
||||||
color: #ff4d4f;
|
color: #ff4d4f;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 分隔线样式 */
|
/* 分隔线样式 */
|
||||||
.divider {
|
.divider {
|
||||||
height: 8px;
|
height: 8px;
|
||||||
background-color: #f9f9f9;
|
background-color: #f9f9f9;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 空状态提示样式 */
|
/* 空状态提示样式 */
|
||||||
.empty-tip {
|
.empty-tip {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
color: #999;
|
color: #999;
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
margin-top: 50px;
|
margin-top: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 修复输入框占位符样式 */
|
/* 修复输入框占位符样式 */
|
||||||
input::-webkit-input-placeholder {
|
input::-webkit-input-placeholder {
|
||||||
color: #ccc;
|
color: #ccc;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -8,7 +8,7 @@
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
background-color: #F5F5F5;
|
background-color: #f5f5f5;
|
||||||
padding-top: 10px;
|
padding-top: 10px;
|
||||||
/* 为固定导航栏预留空间 */
|
/* 为固定导航栏预留空间 */
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
@@ -45,6 +45,7 @@
|
|||||||
color: #333;
|
color: #333;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
margin-right: 45px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.nav-icon {
|
.nav-icon {
|
||||||
@@ -166,7 +167,7 @@
|
|||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border-top: 1px solid #eee;
|
border-top: 1px solid #eee;
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 0;
|
bottom: -1px;
|
||||||
left: 0;
|
left: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
@@ -260,10 +261,9 @@
|
|||||||
padding-left: 1em;
|
padding-left: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* 可点击文档名 */
|
/* 可点击文档名 */
|
||||||
.doc-name-link {
|
.doc-name-link {
|
||||||
color: #007AFF;
|
color: #007aff;
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
margin-right: 16rpx;
|
margin-right: 16rpx;
|
||||||
font-size: 10rpx;
|
font-size: 10rpx;
|
||||||
@@ -301,7 +301,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.doc-name-link {
|
.doc-name-link {
|
||||||
color: #007AFF;
|
color: #007aff;
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
margin-right: 16rpx;
|
margin-right: 16rpx;
|
||||||
font-size: clamp(13px, 3vw, 15px);
|
font-size: clamp(13px, 3vw, 15px);
|
||||||
@@ -344,6 +344,28 @@
|
|||||||
transform: scale(0.95);
|
transform: scale(0.95);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.loading-history {
|
||||||
|
text-align: center;
|
||||||
|
padding: 20rpx;
|
||||||
|
color: #999;
|
||||||
|
font-size: 28rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-more-history {
|
||||||
|
text-align: center;
|
||||||
|
padding: 20rpx;
|
||||||
|
color: #ccc;
|
||||||
|
font-size: 24rpx;
|
||||||
|
}
|
||||||
|
|
||||||
|
.debug-info {
|
||||||
|
color: #888;
|
||||||
|
font-size: 12px;
|
||||||
|
margin-top: 5px;
|
||||||
|
border-top: 1px dashed #eee;
|
||||||
|
padding-top: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
/* ============= 小屏设备适配 ============= */
|
/* ============= 小屏设备适配 ============= */
|
||||||
@media (max-width: 600px) {
|
@media (max-width: 600px) {
|
||||||
.message-content {
|
.message-content {
|
||||||
|
|||||||
@@ -6,9 +6,13 @@ import {
|
|||||||
|
|
||||||
const service = axios.create({
|
const service = axios.create({
|
||||||
// baseURL: 'http://localhost:9090/dev-api/aitutor/aichat',
|
// baseURL: 'http://localhost:9090/dev-api/aitutor/aichat',
|
||||||
baseURL: 'http://localhost:8088/aitutor/aichat',
|
// baseURL: 'http://localhost:8088/aitutor/aichat',
|
||||||
// baseURL: 'http://localhost:8080/aitutor/aichat',
|
// baseURL: 'http://localhost:8080/aitutor/aichat',
|
||||||
|
baseURL: 'http://localhost:8088',
|
||||||
timeout: 15000,
|
timeout: 15000,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 请求拦截器:统一加 token
|
// 请求拦截器:统一加 token
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// src/utils/ai_stream.js (H5 优化版)
|
// src/utils/ai_stream.js
|
||||||
import {
|
import {
|
||||||
getToken
|
getToken
|
||||||
} from '@/utils/auth';
|
} from '@/utils/auth';
|
||||||
@@ -23,7 +23,6 @@ export function createChatStream(params) {
|
|||||||
const fetchPromise = fetch(url, {
|
const fetchPromise = fetch(url, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Accept': 'text/event-stream',
|
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'Authorization': `Bearer ${token}`,
|
'Authorization': `Bearer ${token}`,
|
||||||
'X-Request-ID': requestId
|
'X-Request-ID': requestId
|
||||||
@@ -32,26 +31,27 @@ export function createChatStream(params) {
|
|||||||
query: params.prompt,
|
query: params.prompt,
|
||||||
user_id: params.userId,
|
user_id: params.userId,
|
||||||
user_name: params.userName,
|
user_name: params.userName,
|
||||||
user_token: params.user_token || '123',
|
user_token: params.user_token || '123',
|
||||||
user_role: 'student',
|
user_role: 'student',
|
||||||
conversation_id: params.conversationId || null,
|
conversation_id: params.conversationId || null,
|
||||||
}),
|
}),
|
||||||
signal: controller.signal
|
signal: controller.signal
|
||||||
})
|
})
|
||||||
.then(resp => {
|
.then(response => {
|
||||||
if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
|
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
||||||
if (!resp.body) throw new Error('Response body is null');
|
if (!response.body) throw new Error('Response body is null');
|
||||||
return resp.body;
|
return {
|
||||||
|
reader: response.body.getReader(),
|
||||||
|
decoder: new TextDecoder('utf-8')
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
return fetchPromise.then(body => ({
|
return {
|
||||||
stream: Promise.resolve({
|
stream: fetchPromise,
|
||||||
reader: body.getReader(),
|
cancel: (reason) => {
|
||||||
decoder: new TextDecoder('utf-8')
|
if (!controller.signal.aborted) {
|
||||||
}),
|
controller.abort(reason);
|
||||||
cancel: reason => !controller.signal.aborted && controller.abort(reason)
|
}
|
||||||
})).catch(err => ({
|
}
|
||||||
stream: Promise.reject(err),
|
};
|
||||||
cancel: () => {}
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user