chat.ts 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299
  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: '一个账号可以领取几次0元试用?', answer: '每个手机号可分别领取一次【老师端】和一次【学生端】的免费试用,每端试用期均为2天。' },
  16. { question: '要如何安装软件呢?', answer: '老师端:无需安装,直接在电脑、一体机或希沃等白板的浏览器中访问 https://kt.colexiu.com 即可。推荐使用谷歌浏览器,以获得最佳体验。学生端:可通过手机或平板在应用市场搜索【音乐数字课堂】下载安装,或访问 https://kt.colexiu.com/classroom-app/#/download 获取安装包。' },
  17. { question: '我想有定制化的需求,怎么办?', answer: '我们为会员提供一次定制化曲目服务。如您有需要,可添加专属客服微信:klx569569 进行咨询。' },
  18. { question: '如何进行商务合作?', answer: '感谢您的关注!如有商务合作需求,欢迎添加专属客服微信 klx569569,我们会尽快与您沟通。' },
  19. ];
  20. Page({
  21. data: {
  22. messages: [] as Message[],
  23. inputValue: '',
  24. scrollToMessage: '',
  25. userAvatar: '',
  26. isLoading: false,
  27. avatarError: false,
  28. keyboardHeight: 0,
  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: '您好,欢迎来到音乐数字课堂!请问有什么可以帮您的吗?',
  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. onAvatarError() {
  79. this.setData({ avatarError: true });
  80. },
  81. // 输入框变化
  82. onInput(e: WechatMiniprogram.Input) {
  83. this.setData({
  84. inputValue: e.detail.value
  85. });
  86. },
  87. onKeyboardHeightChange(e: WechatMiniprogram.InputKeyboardHeightChange) {
  88. const keyboardHeight = e.detail.height || 0;
  89. if (keyboardHeight === this.data.keyboardHeight) return;
  90. this.setData({ keyboardHeight }, () => {
  91. this.scrollToBottom();
  92. });
  93. },
  94. // 发送文字消息
  95. sendTextMessage() {
  96. const { inputValue, isLoading } = this.data;
  97. if (!inputValue.trim() || isLoading) return;
  98. const content = inputValue.trim();
  99. // 添加用户消息
  100. this.addUserMessage('text', content);
  101. // 清空输入框
  102. this.setData({ inputValue: '' });
  103. // 发送到服务器
  104. this.sendMessageToServer(content, 'text');
  105. },
  106. // 添加用户消息
  107. addUserMessage(contentType: 'text' | 'image', content: string) {
  108. const { messages } = this.data;
  109. const newMessage: Message = {
  110. id: this.generateId(),
  111. type: 'user',
  112. contentType,
  113. content,
  114. timestamp: Date.now()
  115. };
  116. this.setData({
  117. messages: [...messages, newMessage]
  118. }, () => {
  119. this.scrollToBottom();
  120. });
  121. },
  122. // 选择图片
  123. chooseImage() {
  124. wx.chooseMedia({
  125. count: 1,
  126. mediaType: ['image'],
  127. sourceType: ['album', 'camera'],
  128. success: (res) => {
  129. const tempFilePath = res.tempFiles[0].tempFilePath;
  130. this.uploadImage(tempFilePath);
  131. },
  132. fail: (err) => {
  133. console.error('选择图片失败:', err);
  134. wx.showToast({
  135. title: '选择图片失败',
  136. icon: 'none'
  137. });
  138. }
  139. });
  140. },
  141. // 上传图片
  142. uploadImage(filePath: string) {
  143. // 先显示本地图片
  144. this.addUserMessage('image', filePath);
  145. // 获取全局配置
  146. const app = getApp<IAppOption>();
  147. const baseUrl = app.globalData?.baseUrl || 'https://test.kt.colexiu.com';
  148. // 上传图片到服务器
  149. wx.uploadFile({
  150. url: `${baseUrl}/edu-app/ai/chat/upload`,
  151. filePath: filePath,
  152. name: 'file',
  153. header: {
  154. 'Authorization': wx.getStorageSync('token') || ''
  155. },
  156. success: (res) => {
  157. try {
  158. const data = JSON.parse(res.data);
  159. if (data.code === 200) {
  160. // 上传成功,发送图片URL到聊天
  161. this.sendMessageToServer(data.data.url, 'image');
  162. } else {
  163. throw new Error(data.msg || '上传失败');
  164. }
  165. } catch (err) {
  166. console.error('解析上传响应失败:', err);
  167. this.addReplyMessage('图片上传失败,请重试');
  168. }
  169. },
  170. fail: (err) => {
  171. console.error('上传图片失败:', err);
  172. // 模拟成功,直接回复
  173. setTimeout(() => {
  174. this.addReplyMessage('我已收到您的图片,请问有什么可以帮助您的吗?');
  175. }, 1000);
  176. }
  177. });
  178. },
  179. // 发送消息到服务器
  180. async sendMessageToServer(content: string, contentType: 'text' | 'image') {
  181. this.setData({ isLoading: true });
  182. // 获取用户ID(优先使用openid,其次使用token中的用户ID)
  183. const userInfo = wx.getStorageSync('userInfo') || {};
  184. const userId = userInfo.openId || userInfo.userId || wx.getStorageSync('openId');
  185. // 构建消息内容(图片发送固定提示语)
  186. const messageContent = contentType === 'image' ? '我发送了一张图片' : content;
  187. try {
  188. // 调用客服消息接口
  189. const {data}: any = await api_cozeAgent({
  190. message: messageContent,
  191. userId: userId
  192. });
  193. if (data.code === 200 && data.data) {
  194. // 使用接口返回的回复内容
  195. const reply =data.data || '您好,我已经收到您的问题。';
  196. this.addReplyMessage(reply);
  197. } else {
  198. // 接口返回错误,使用默认回复
  199. this.handleDefaultReply(contentType);
  200. }
  201. } catch (err) {
  202. console.error('客服接口调用失败:', err);
  203. // 接口调用失败,使用默认回复
  204. this.handleDefaultReply(contentType);
  205. } finally {
  206. this.setData({ isLoading: false });
  207. }
  208. },
  209. // 处理默认回复(当接口调用失败时使用)
  210. handleDefaultReply(contentType: 'text' | 'image') {
  211. let reply = '';
  212. if (contentType === 'image') {
  213. reply = '我已收到您的图片,请问有什么可以帮助您的吗?';
  214. } else {
  215. reply = '您好,我已经收到您的问题。我们的客服人员会尽快为您解答。如果您有其他问题,也可以继续提问。';
  216. }
  217. this.addReplyMessage(reply);
  218. },
  219. // 添加AI回复消息
  220. addReplyMessage(content: string) {
  221. const { messages } = this.data;
  222. const replyMessage: Message = {
  223. id: this.generateId(),
  224. type: 'reply',
  225. contentType: 'text',
  226. content,
  227. timestamp: Date.now()
  228. };
  229. this.setData({
  230. messages: [...messages, replyMessage]
  231. }, () => {
  232. this.scrollToBottom();
  233. });
  234. },
  235. // 点击常见问题
  236. onFaqTap(e: WechatMiniprogram.Tap) {
  237. const index = e.currentTarget.dataset.index;
  238. const faq = FAQ_LIST[index];
  239. if (faq) {
  240. // 添加用户选择的问题
  241. this.addUserMessage('text', faq.question);
  242. // 直接回复固定内容
  243. setTimeout(() => {
  244. this.addReplyMessage(faq.answer);
  245. }, 500);
  246. }
  247. },
  248. // 点击图片预览
  249. onImageTap(e: any) {
  250. const url = e.currentTarget.dataset.url;
  251. if (url) {
  252. wx.previewImage({
  253. urls: [url],
  254. current: url
  255. });
  256. }
  257. }
  258. });