chat.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  1. // pages/chat/chat.js
  2. const chatApi = require('../../api/chat');
  3. const userUtil = require('../../utils/user');
  4. Page({
  5. data: {
  6. userId: null,
  7. sessionId: null,
  8. messages: [],
  9. inputMessage: '',
  10. loading: false,
  11. sending: false,
  12. scrollIntoView: '' // 用于scroll-view滚动到底部
  13. },
  14. onLoad(options) {
  15. console.log('聊天页面加载, options:', options);
  16. const userInfo = userUtil.getUserInfo();
  17. console.log('用户信息:', userInfo);
  18. if (!userInfo || !userInfo.userId) {
  19. console.error('用户未登录或userId为空');
  20. wx.showToast({
  21. title: '请先登录',
  22. icon: 'none'
  23. });
  24. setTimeout(() => {
  25. wx.navigateBack();
  26. }, 1500);
  27. return;
  28. }
  29. console.log('设置userId:', userInfo.userId);
  30. this.setData({
  31. userId: userInfo.userId
  32. });
  33. // 优先使用传入的sessionId(如果有)
  34. if (options.sessionId) {
  35. console.log('使用传入的sessionId:', options.sessionId);
  36. this.setData({
  37. sessionId: options.sessionId
  38. });
  39. // 保存sessionId到本地存储
  40. this.saveSessionId(options.sessionId);
  41. this.loadMessageHistory();
  42. } else {
  43. // 尝试从本地存储加载sessionId
  44. const savedSessionId = this.getSavedSessionId(userInfo.userId);
  45. if (savedSessionId) {
  46. console.log('从本地存储加载sessionId:', savedSessionId);
  47. this.setData({
  48. sessionId: savedSessionId
  49. });
  50. this.loadMessageHistory();
  51. } else {
  52. // 创建新会话
  53. console.log('创建新会话');
  54. this.createSession();
  55. }
  56. }
  57. },
  58. onShow() {
  59. // 检查用户是否仍然登录
  60. const userInfo = userUtil.getUserInfo();
  61. if (!userInfo || !userInfo.userId) {
  62. // 用户已退出登录,清除会话数据
  63. if (this.data.sessionId) {
  64. this.clearSavedSessionId(this.data.userId);
  65. }
  66. return;
  67. }
  68. // 如果userId发生变化(可能是切换了账号),重新加载
  69. if (this.data.userId && userInfo.userId !== this.data.userId) {
  70. console.log('检测到用户切换,重新加载会话');
  71. this.setData({
  72. userId: userInfo.userId,
  73. sessionId: null,
  74. messages: []
  75. });
  76. const savedSessionId = this.getSavedSessionId(userInfo.userId);
  77. if (savedSessionId) {
  78. this.setData({ sessionId: savedSessionId });
  79. this.loadMessageHistory();
  80. } else {
  81. this.createSession();
  82. }
  83. }
  84. },
  85. // 创建新会话
  86. async createSession() {
  87. try {
  88. console.log('开始创建会话, userId:', this.data.userId);
  89. if (!this.data.userId) {
  90. throw new Error('userId为空,请先登录');
  91. }
  92. this.setData({ loading: true });
  93. const session = await chatApi.createSession(this.data.userId);
  94. console.log('创建会话成功:', session);
  95. if (!session || !session.sessionId) {
  96. throw new Error('创建会话失败,未返回sessionId');
  97. }
  98. // 保存sessionId到本地存储
  99. this.saveSessionId(session.sessionId);
  100. this.setData({
  101. sessionId: session.sessionId,
  102. messages: [],
  103. loading: false
  104. });
  105. console.log('会话创建完成, sessionId:', session.sessionId);
  106. } catch (error) {
  107. console.error('创建会话失败:', error);
  108. wx.showToast({
  109. title: '创建会话失败: ' + (error.message || error),
  110. icon: 'none',
  111. duration: 2000
  112. });
  113. this.setData({ loading: false });
  114. throw error; // 重新抛出错误,让调用方知道失败
  115. }
  116. },
  117. // 保存sessionId到本地存储
  118. saveSessionId(sessionId) {
  119. if (!this.data.userId || !sessionId) {
  120. return;
  121. }
  122. const key = `chat_sessionId_${this.data.userId}`;
  123. wx.setStorageSync(key, sessionId);
  124. console.log('保存sessionId到本地存储:', key, sessionId);
  125. },
  126. // 从本地存储获取sessionId
  127. getSavedSessionId(userId) {
  128. if (!userId) {
  129. return null;
  130. }
  131. const key = `chat_sessionId_${userId}`;
  132. const sessionId = wx.getStorageSync(key);
  133. console.log('从本地存储获取sessionId:', key, sessionId);
  134. return sessionId || null;
  135. },
  136. // 清除本地存储的sessionId
  137. clearSavedSessionId(userId) {
  138. if (!userId) {
  139. return;
  140. }
  141. const key = `chat_sessionId_${userId}`;
  142. wx.removeStorageSync(key);
  143. console.log('清除本地存储的sessionId:', key);
  144. },
  145. // 加载消息历史
  146. async loadMessageHistory() {
  147. if (!this.data.sessionId || !this.data.userId) {
  148. console.warn('无法加载消息历史:sessionId或userId为空');
  149. return;
  150. }
  151. try {
  152. this.setData({ loading: true });
  153. console.log('加载消息历史:', this.data.sessionId, this.data.userId);
  154. const messages = await chatApi.getMessageHistory(this.data.sessionId, this.data.userId);
  155. console.log('收到消息历史:', messages);
  156. // 确保消息格式正确(处理时间戳)
  157. const formattedMessages = (messages || []).map(msg => {
  158. return {
  159. role: msg.role || 'user',
  160. content: msg.content || '',
  161. timestamp: msg.timestamp || new Date().toISOString()
  162. };
  163. });
  164. this.setData({
  165. messages: formattedMessages,
  166. loading: false
  167. });
  168. // 延迟滚动到底部,确保DOM已更新
  169. setTimeout(() => {
  170. this.scrollToBottom();
  171. }, 200);
  172. } catch (error) {
  173. console.error('加载消息历史失败:', error);
  174. // 如果是会话不存在或无权访问,清除本地保存的sessionId
  175. if (error.message && (error.message.includes('不存在') || error.message.includes('无权访问'))) {
  176. console.log('会话无效,清除本地保存的sessionId');
  177. this.clearSavedSessionId(this.data.userId);
  178. this.setData({
  179. sessionId: null,
  180. messages: []
  181. });
  182. // 创建新会话
  183. this.createSession();
  184. } else {
  185. wx.showToast({
  186. title: '加载消息失败',
  187. icon: 'none'
  188. });
  189. this.setData({ loading: false });
  190. }
  191. }
  192. },
  193. // 输入消息
  194. onInput(e) {
  195. this.setData({
  196. inputMessage: e.detail.value
  197. });
  198. },
  199. // 发送消息
  200. async sendMessage() {
  201. const message = this.data.inputMessage.trim();
  202. if (!message) {
  203. wx.showToast({
  204. title: '请输入消息',
  205. icon: 'none'
  206. });
  207. return;
  208. }
  209. // 检查userId
  210. if (!this.data.userId) {
  211. console.error('userId为空,请先登录');
  212. wx.showToast({
  213. title: '请先登录',
  214. icon: 'none'
  215. });
  216. return;
  217. }
  218. // 如果没有sessionId,先创建会话
  219. if (!this.data.sessionId) {
  220. console.log('没有sessionId,创建新会话');
  221. try {
  222. await this.createSession();
  223. if (!this.data.sessionId) {
  224. wx.showToast({
  225. title: '创建会话失败',
  226. icon: 'none'
  227. });
  228. return;
  229. }
  230. } catch (error) {
  231. console.error('创建会话失败:', error);
  232. wx.showToast({
  233. title: '创建会话失败: ' + error,
  234. icon: 'none',
  235. duration: 2000
  236. });
  237. return;
  238. }
  239. }
  240. try {
  241. this.setData({
  242. sending: true,
  243. inputMessage: ''
  244. });
  245. console.log('开始发送消息:', {
  246. sessionId: this.data.sessionId,
  247. userId: this.data.userId,
  248. message: message
  249. });
  250. // 添加用户消息到列表
  251. const userMessage = {
  252. role: 'user',
  253. content: message,
  254. timestamp: new Date().toISOString()
  255. };
  256. const currentMessages = [...this.data.messages, userMessage];
  257. this.setData({
  258. messages: currentMessages
  259. });
  260. this.scrollToBottom();
  261. // 发送消息到服务器
  262. console.log('调用API发送消息...');
  263. const response = await chatApi.sendMessage(this.data.sessionId, message, this.data.userId);
  264. console.log('收到API响应:', response);
  265. // 更新会话ID(如果是新创建的)
  266. if (response.session && response.session.sessionId) {
  267. const newSessionId = response.session.sessionId;
  268. console.log('更新sessionId:', newSessionId);
  269. this.setData({
  270. sessionId: newSessionId
  271. });
  272. // 保存新的sessionId到本地存储
  273. this.saveSessionId(newSessionId);
  274. }
  275. // 添加AI回复到列表
  276. if (response.assistantMessage) {
  277. console.log('收到AI回复:', response.assistantMessage.content);
  278. this.setData({
  279. messages: [...currentMessages, response.assistantMessage]
  280. });
  281. this.scrollToBottom();
  282. } else {
  283. console.warn('未收到AI回复');
  284. wx.showToast({
  285. title: '未收到AI回复',
  286. icon: 'none'
  287. });
  288. }
  289. this.setData({ sending: false });
  290. } catch (error) {
  291. console.error('发送消息失败:', error);
  292. console.error('错误详情:', JSON.stringify(error));
  293. wx.showToast({
  294. title: '发送失败: ' + (error.message || error || '未知错误'),
  295. icon: 'none',
  296. duration: 3000
  297. });
  298. // 移除用户消息(发送失败)
  299. const messages = this.data.messages.filter(m => m.content !== message || m.role !== 'user');
  300. this.setData({
  301. messages: messages,
  302. sending: false,
  303. inputMessage: message
  304. });
  305. }
  306. },
  307. // 滚动到底部
  308. scrollToBottom() {
  309. // 使用scroll-view的scroll-into-view属性滚动到底部
  310. if (this.data.messages && this.data.messages.length > 0) {
  311. const lastIndex = this.data.messages.length - 1;
  312. const scrollIntoView = `msg-${lastIndex}`;
  313. this.setData({
  314. scrollIntoView: scrollIntoView
  315. });
  316. // 清除scrollIntoView,以便下次可以再次触发
  317. setTimeout(() => {
  318. this.setData({
  319. scrollIntoView: ''
  320. });
  321. }, 300);
  322. }
  323. },
  324. // 格式化时间
  325. formatTime(date) {
  326. if (!date) return '';
  327. try {
  328. const d = typeof date === 'string' ? new Date(date) : date;
  329. if (isNaN(d.getTime())) return '';
  330. const hours = d.getHours().toString().padStart(2, '0');
  331. const minutes = d.getMinutes().toString().padStart(2, '0');
  332. return `${hours}:${minutes}`;
  333. } catch (e) {
  334. return '';
  335. }
  336. }
  337. });