chat.ts 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  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. avatarError: false,
  30. },
  31. onLoad() {
  32. this.loadUserInfo();
  33. this.initWelcomeMessages();
  34. },
  35. // 加载用户信息
  36. loadUserInfo() {
  37. const userInfo = wx.getStorageSync('userInfo');
  38. if (userInfo) {
  39. this.setData({
  40. userAvatar: userInfo.avatarUrl || ''
  41. });
  42. }
  43. },
  44. // 初始化欢迎消息
  45. initWelcomeMessages() {
  46. const messages: Message[] = [];
  47. // 第一条欢迎消息
  48. messages.push({
  49. id: this.generateId(),
  50. type: 'ai',
  51. contentType: 'text',
  52. content: '您好!欢迎来到音乐数字AI客服中心。我是您的专属客服小助手,很高兴为您服务!请问有什么可以帮助您的吗?',
  53. timestamp: Date.now()
  54. });
  55. // 第二条常见问题卡片
  56. messages.push({
  57. id: this.generateId(),
  58. type: 'ai',
  59. contentType: 'faq',
  60. content: '常见问题',
  61. questions: FAQ_LIST.map(item => item.question),
  62. timestamp: Date.now() + 1
  63. });
  64. this.setData({ messages }, () => {
  65. this.scrollToBottom();
  66. });
  67. },
  68. // 生成唯一ID
  69. generateId(): string {
  70. return Date.now().toString(36) + Math.random().toString(36).substr(2);
  71. },
  72. // 滚动到底部
  73. scrollToBottom() {
  74. this.setData({
  75. scrollToMessage: 'last-message'
  76. });
  77. },
  78. // 头像加载失败
  79. onAvatarError() {
  80. this.setData({ avatarError: true });
  81. },
  82. // 输入框变化
  83. onInput(e: WechatMiniprogram.Input) {
  84. this.setData({
  85. inputValue: e.detail.value
  86. });
  87. },
  88. // 发送文字消息
  89. sendTextMessage() {
  90. const { inputValue, isLoading } = this.data;
  91. if (!inputValue.trim() || isLoading) return;
  92. const content = inputValue.trim();
  93. // 添加用户消息
  94. this.addUserMessage('text', content);
  95. // 清空输入框
  96. this.setData({ inputValue: '' });
  97. // 发送到服务器
  98. this.sendMessageToServer(content, 'text');
  99. },
  100. // 添加用户消息
  101. addUserMessage(contentType: 'text' | 'image', content: string) {
  102. const { messages } = this.data;
  103. const newMessage: Message = {
  104. id: this.generateId(),
  105. type: 'user',
  106. contentType,
  107. content,
  108. timestamp: Date.now()
  109. };
  110. this.setData({
  111. messages: [...messages, newMessage]
  112. }, () => {
  113. this.scrollToBottom();
  114. });
  115. },
  116. // 选择图片
  117. chooseImage() {
  118. wx.chooseMedia({
  119. count: 1,
  120. mediaType: ['image'],
  121. sourceType: ['album', 'camera'],
  122. success: (res) => {
  123. const tempFilePath = res.tempFiles[0].tempFilePath;
  124. this.uploadImage(tempFilePath);
  125. },
  126. fail: (err) => {
  127. console.error('选择图片失败:', err);
  128. wx.showToast({
  129. title: '选择图片失败',
  130. icon: 'none'
  131. });
  132. }
  133. });
  134. },
  135. // 上传图片
  136. uploadImage(filePath: string) {
  137. // 先显示本地图片
  138. this.addUserMessage('image', filePath);
  139. // 获取全局配置
  140. const app = getApp<IAppOption>();
  141. const baseUrl = app.globalData?.baseUrl || 'https://test.kt.colexiu.com';
  142. // 上传图片到服务器
  143. wx.uploadFile({
  144. url: `${baseUrl}/edu-app/ai/chat/upload`,
  145. filePath: filePath,
  146. name: 'file',
  147. header: {
  148. 'Authorization': wx.getStorageSync('token') || ''
  149. },
  150. success: (res) => {
  151. try {
  152. const data = JSON.parse(res.data);
  153. if (data.code === 200) {
  154. // 上传成功,发送图片URL到聊天
  155. this.sendMessageToServer(data.data.url, 'image');
  156. } else {
  157. throw new Error(data.msg || '上传失败');
  158. }
  159. } catch (err) {
  160. console.error('解析上传响应失败:', err);
  161. this.addReplyMessage('图片上传失败,请重试');
  162. }
  163. },
  164. fail: (err) => {
  165. console.error('上传图片失败:', err);
  166. // 模拟成功,直接回复
  167. setTimeout(() => {
  168. this.addReplyMessage('我已收到您的图片,请问有什么可以帮助您的吗?');
  169. }, 1000);
  170. }
  171. });
  172. },
  173. // 发送消息到服务器
  174. async sendMessageToServer(content: string, contentType: 'text' | 'image') {
  175. this.setData({ isLoading: true });
  176. // 获取用户ID(优先使用openid,其次使用token中的用户ID)
  177. const userInfo = wx.getStorageSync('userInfo') || {};
  178. const userId = userInfo.openId || userInfo.userId || wx.getStorageSync('openId');
  179. // 构建消息内容(图片发送固定提示语)
  180. const messageContent = contentType === 'image' ? '我发送了一张图片' : content;
  181. try {
  182. // 调用客服消息接口
  183. const res: any = await api_cozeAgent({
  184. message: messageContent,
  185. userId: userId
  186. });
  187. if (res.code === 200 && res.data) {
  188. // 使用接口返回的回复内容
  189. const reply = res.data.reply || res.data.content || res.data.message || '您好,我已经收到您的问题。';
  190. this.addReplyMessage(reply);
  191. } else {
  192. // 接口返回错误,使用默认回复
  193. this.handleDefaultReply(contentType);
  194. }
  195. } catch (err) {
  196. console.error('客服接口调用失败:', err);
  197. // 接口调用失败,使用默认回复
  198. this.handleDefaultReply(contentType);
  199. } finally {
  200. this.setData({ isLoading: false });
  201. }
  202. },
  203. // 处理默认回复(当接口调用失败时使用)
  204. handleDefaultReply(contentType: 'text' | 'image') {
  205. let reply = '';
  206. if (contentType === 'image') {
  207. reply = '我已收到您的图片,请问有什么可以帮助您的吗?';
  208. } else {
  209. reply = '您好,我已经收到您的问题。我们的客服人员会尽快为您解答。如果您有其他问题,也可以继续提问。';
  210. }
  211. this.addReplyMessage(reply);
  212. },
  213. // 添加AI回复消息
  214. addReplyMessage(content: string) {
  215. const { messages } = this.data;
  216. const replyMessage: Message = {
  217. id: this.generateId(),
  218. type: 'reply',
  219. contentType: 'text',
  220. content,
  221. timestamp: Date.now()
  222. };
  223. this.setData({
  224. messages: [...messages, replyMessage]
  225. }, () => {
  226. this.scrollToBottom();
  227. });
  228. },
  229. // 点击常见问题
  230. onFaqTap(e: WechatMiniprogram.Tap) {
  231. const index = e.currentTarget.dataset.index;
  232. const faq = FAQ_LIST[index];
  233. if (faq) {
  234. // 添加用户选择的问题
  235. this.addUserMessage('text', faq.question);
  236. // 直接回复固定内容
  237. setTimeout(() => {
  238. this.addReplyMessage(faq.answer);
  239. }, 500);
  240. }
  241. },
  242. // 点击图片预览
  243. onImageTap(e: any) {
  244. const url = e.currentTarget.dataset.url;
  245. if (url) {
  246. wx.previewImage({
  247. urls: [url],
  248. current: url
  249. });
  250. }
  251. }
  252. });