chat.ts 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. interface Message {
  2. id: string;
  3. type: 'ai' | 'user' | 'reply';
  4. contentType: 'text' | 'image' | 'faq';
  5. content: string;
  6. questions?: string[];
  7. timestamp: number;
  8. }
  9. interface FAQItem {
  10. question: string;
  11. answer: string;
  12. }
  13. import { api_cozeAgent } from '../../api/login';
  14. const FAQ_LIST: FAQItem[] = [
  15. { question: '产品价格是多少?', answer: '我们的产品价格根据不同的套餐有所不同。基础版¥99/月,专业版¥199/月,企业版¥499/月。您可以根据自己的需求选择合适的套餐。' },
  16. { question: '如何购买课程?', answer: '您可以通过以下步骤购买课程:1. 在首页浏览课程列表;2. 点击心仪的课程进入详情页;3. 点击"立即购买"按钮;4. 选择支付方式完成支付。' },
  17. { question: '支持哪些设备?', answer: '我们的产品支持多种设备:iOS设备(iPhone、iPad)、Android设备(手机、平板)、Windows电脑、Mac电脑,以及主流的智能电视。' },
  18. { question: '有没有试用版本?', answer: '有的!我们为所有新用户提供7天免费试用期。试用期间您可以体验全部专业版功能,无需绑定支付方式。试用期结束后,您可以选择是否订阅。' },
  19. { question: '售后服务政策', answer: '我们提供完善的售后服务:1. 7天无理由退款;2. 产品问题30天内免费换新;3. 终身技术支持;4. 专属客服一对一服务。如有任何问题,请随时联系我们。' },
  20. { question: '开具发票', answer: '我们可以为您开具增值税普通发票或专用发票。请在购买后30天内,在"我的订单"页面点击"申请开票",填写相关信息后,我们会在5个工作日内将发票发送至您的邮箱。' },
  21. ];
  22. Page({
  23. data: {
  24. messages: [] as Message[],
  25. inputValue: '',
  26. scrollToMessage: '',
  27. userAvatar: '',
  28. isLoading: false,
  29. },
  30. onLoad() {
  31. this.loadUserInfo();
  32. this.initWelcomeMessages();
  33. },
  34. // 加载用户信息
  35. loadUserInfo() {
  36. const userInfo = wx.getStorageSync('userInfo');
  37. if (userInfo) {
  38. this.setData({
  39. userAvatar: userInfo.avatarUrl || ''
  40. });
  41. }
  42. },
  43. // 初始化欢迎消息
  44. initWelcomeMessages() {
  45. const messages: Message[] = [];
  46. // 第一条欢迎消息
  47. messages.push({
  48. id: this.generateId(),
  49. type: 'ai',
  50. contentType: 'text',
  51. content: '您好!欢迎来到音乐数字AI客服中心。我是您的专属客服小助手,很高兴为您服务!请问有什么可以帮助您的吗?',
  52. timestamp: Date.now()
  53. });
  54. // 第二条常见问题卡片
  55. messages.push({
  56. id: this.generateId(),
  57. type: 'ai',
  58. contentType: 'faq',
  59. content: '常见问题',
  60. questions: FAQ_LIST.map(item => item.question),
  61. timestamp: Date.now() + 1
  62. });
  63. this.setData({ messages }, () => {
  64. this.scrollToBottom();
  65. });
  66. },
  67. // 生成唯一ID
  68. generateId(): string {
  69. return Date.now().toString(36) + Math.random().toString(36).substr(2);
  70. },
  71. // 滚动到底部
  72. scrollToBottom() {
  73. this.setData({
  74. scrollToMessage: 'last-message'
  75. });
  76. },
  77. // 输入框变化
  78. onInput(e: WechatMiniprogram.Input) {
  79. this.setData({
  80. inputValue: e.detail.value
  81. });
  82. },
  83. // 发送文字消息
  84. sendTextMessage() {
  85. const { inputValue, isLoading } = this.data;
  86. if (!inputValue.trim() || isLoading) return;
  87. const content = inputValue.trim();
  88. // 添加用户消息
  89. this.addUserMessage('text', content);
  90. // 清空输入框
  91. this.setData({ inputValue: '' });
  92. // 发送到服务器
  93. this.sendMessageToServer(content, 'text');
  94. },
  95. // 添加用户消息
  96. addUserMessage(contentType: 'text' | 'image', content: string) {
  97. const { messages } = this.data;
  98. const newMessage: Message = {
  99. id: this.generateId(),
  100. type: 'user',
  101. contentType,
  102. content,
  103. timestamp: Date.now()
  104. };
  105. this.setData({
  106. messages: [...messages, newMessage]
  107. }, () => {
  108. this.scrollToBottom();
  109. });
  110. },
  111. // 选择图片
  112. chooseImage() {
  113. wx.chooseMedia({
  114. count: 1,
  115. mediaType: ['image'],
  116. sourceType: ['album', 'camera'],
  117. success: (res) => {
  118. const tempFilePath = res.tempFiles[0].tempFilePath;
  119. this.uploadImage(tempFilePath);
  120. },
  121. fail: (err) => {
  122. console.error('选择图片失败:', err);
  123. wx.showToast({
  124. title: '选择图片失败',
  125. icon: 'none'
  126. });
  127. }
  128. });
  129. },
  130. // 上传图片
  131. uploadImage(filePath: string) {
  132. // 先显示本地图片
  133. this.addUserMessage('image', filePath);
  134. // 获取全局配置
  135. const app = getApp<IAppOption>();
  136. const baseUrl = app.globalData?.baseUrl || 'https://test.kt.colexiu.com';
  137. // 上传图片到服务器
  138. wx.uploadFile({
  139. url: `${baseUrl}/edu-app/ai/chat/upload`,
  140. filePath: filePath,
  141. name: 'file',
  142. header: {
  143. 'Authorization': wx.getStorageSync('token') || ''
  144. },
  145. success: (res) => {
  146. try {
  147. const data = JSON.parse(res.data);
  148. if (data.code === 200) {
  149. // 上传成功,发送图片URL到聊天
  150. this.sendMessageToServer(data.data.url, 'image');
  151. } else {
  152. throw new Error(data.msg || '上传失败');
  153. }
  154. } catch (err) {
  155. console.error('解析上传响应失败:', err);
  156. this.addReplyMessage('图片上传失败,请重试');
  157. }
  158. },
  159. fail: (err) => {
  160. console.error('上传图片失败:', err);
  161. // 模拟成功,直接回复
  162. setTimeout(() => {
  163. this.addReplyMessage('我已收到您的图片,请问有什么可以帮助您的吗?');
  164. }, 1000);
  165. }
  166. });
  167. },
  168. // 发送消息到服务器
  169. async sendMessageToServer(content: string, contentType: 'text' | 'image') {
  170. this.setData({ isLoading: true });
  171. // 获取用户ID(优先使用openid,其次使用token中的用户ID)
  172. const userInfo = wx.getStorageSync('userInfo') || {};
  173. const userId = userInfo.openId || userInfo.userId || wx.getStorageSync('openid') || '';
  174. // 构建消息内容(图片发送固定提示语)
  175. const messageContent = contentType === 'image' ? '我发送了一张图片' : content;
  176. try {
  177. // 调用客服消息接口
  178. const res: any = await api_cozeAgent({
  179. message: messageContent,
  180. userId: userId
  181. });
  182. if (res.code === 200 && res.data) {
  183. // 使用接口返回的回复内容
  184. const reply = res.data.reply || res.data.content || res.data.message || '您好,我已经收到您的问题。';
  185. this.addReplyMessage(reply);
  186. } else {
  187. // 接口返回错误,使用默认回复
  188. this.handleDefaultReply(contentType);
  189. }
  190. } catch (err) {
  191. console.error('客服接口调用失败:', err);
  192. // 接口调用失败,使用默认回复
  193. this.handleDefaultReply(contentType);
  194. } finally {
  195. this.setData({ isLoading: false });
  196. }
  197. },
  198. // 处理默认回复(当接口调用失败时使用)
  199. handleDefaultReply(contentType: 'text' | 'image') {
  200. let reply = '';
  201. if (contentType === 'image') {
  202. reply = '我已收到您的图片,请问有什么可以帮助您的吗?';
  203. } else {
  204. reply = '您好,我已经收到您的问题。我们的客服人员会尽快为您解答。如果您有其他问题,也可以继续提问。';
  205. }
  206. this.addReplyMessage(reply);
  207. },
  208. // 添加AI回复消息
  209. addReplyMessage(content: string) {
  210. const { messages } = this.data;
  211. const replyMessage: Message = {
  212. id: this.generateId(),
  213. type: 'reply',
  214. contentType: 'text',
  215. content,
  216. timestamp: Date.now()
  217. };
  218. this.setData({
  219. messages: [...messages, replyMessage]
  220. }, () => {
  221. this.scrollToBottom();
  222. });
  223. },
  224. // 点击常见问题
  225. onFaqTap(e: WechatMiniprogram.Tap) {
  226. const index = e.currentTarget.dataset.index;
  227. const faq = FAQ_LIST[index];
  228. if (faq) {
  229. // 添加用户选择的问题
  230. this.addUserMessage('text', faq.question);
  231. // 直接回复固定内容
  232. setTimeout(() => {
  233. this.addReplyMessage(faq.answer);
  234. }, 500);
  235. }
  236. }
  237. });