chatArea.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609
  1. <template>
  2. <div class="chat" v-loading="loading">
  3. <div class="c_chat" ref="chatRef">
  4. <div class="c_c_i_ai">
  5. <div class="c_c_i_ai_avatar">
  6. <el-avatar
  7. style="width: 100%; height: 100%"
  8. :src="
  9. require('../../../../assets/icon/pblCourse/aiTeacherAvatar.png')
  10. "
  11. fit="fit"
  12. ></el-avatar>
  13. </div>
  14. <div class="c_c_i_ai_message">
  15. <div class="c_c_i_ai_m_top">
  16. <span>小可老师</span>
  17. <span class="chatTime">{{ new Date().toLocaleString().replaceAll('/','-') }}</span>
  18. </div>
  19. <div class="c_c_i_ai_m_bottom">
  20. <div
  21. v-loading="false"
  22. class="c_c_i_ai_m_content chatContent"
  23. >
  24. <p>同学你好,我是你的助教老师小可,你有什么问题要问我吗?</p>
  25. <p style="margin-top:5px">我很擅长协助你探索以下问题哦~</p>
  26. <ul style="color:#3673E8;margin-top: 10px;">
  27. <li style="margin-top: 5px;cursor: pointer;" @click="sendChat('作业思路讨论')">作业思路讨论</li>
  28. <li style="margin-top: 5px;cursor: pointer;" @click="sendChat('回答学习疑惑')">回答学习疑惑</li>
  29. <li style="margin-top: 5px;cursor: pointer;" @click="sendChat('查找资源资料')">查找资源资料</li>
  30. <li style="margin-top: 5px;cursor: pointer;" @click="sendChat('整理学习资料')">整理学习资料</li>
  31. </ul>
  32. </div>
  33. <!-- <span class="coptText" @click="copyText(item.aiContent)"></span> -->
  34. </div>
  35. </div>
  36. </div>
  37. <div class="c_c_item" v-for="item in chatList" :key="item.uid">
  38. <div class="c_c_i_user" v-if="item.content">
  39. <div class="c_c_i_u_message">
  40. <div class="c_c_i_u_m_top">
  41. <span class="chatTime">{{ item.createtime }}</span>
  42. <span>科科</span>
  43. </div>
  44. <div class="c_c_i_u_m_bottom">
  45. <span class="coptText" @click="copyText(item.aiContent)"></span>
  46. <div
  47. class="c_c_i_u_m_content chatContent"
  48. v-html="item.content"
  49. ></div>
  50. </div>
  51. </div>
  52. <div class="c_c_i_u_avatar">
  53. <el-avatar
  54. style="width: 100%; height: 100%"
  55. :src="require('../../../../assets/icon/pblCourse/userAvatar.png')"
  56. fit="fit"
  57. ></el-avatar>
  58. </div>
  59. </div>
  60. <div class="c_c_i_ai">
  61. <div class="c_c_i_ai_avatar">
  62. <el-avatar
  63. style="width: 100%; height: 100%"
  64. :src="
  65. require('../../../../assets/icon/pblCourse/aiTeacherAvatar.png')
  66. "
  67. fit="fit"
  68. ></el-avatar>
  69. </div>
  70. <div class="c_c_i_ai_message">
  71. <div class="c_c_i_ai_m_top">
  72. <span>小可老师</span>
  73. <span class="chatTime">{{item.createtime}}</span>
  74. </div>
  75. <div class="c_c_i_ai_m_bottom">
  76. <div
  77. v-loading="item.loading"
  78. class="c_c_i_ai_m_content chatContent"
  79. v-html="item.aiContent"
  80. ></div>
  81. <span class="coptText" @click="copyText(item.aiContent)"></span>
  82. </div>
  83. </div>
  84. </div>
  85. </div>
  86. </div>
  87. <!-- <div class="c_controls">
  88. <span class="c_controls_item" @click="clearChat">清空对话</span>
  89. </div> -->
  90. <div class="c_bottom" v-loading="chatLoading">
  91. <!-- <div class="c_b_record">
  92. <span></span>
  93. </div> -->
  94. <div class="c_b_inputArea">
  95. <el-input
  96. class="c_b_input"
  97. @keyup.enter.native="send()"
  98. v-model="textValue"
  99. >
  100. </el-input>
  101. <!-- <span></span> -->
  102. </div>
  103. <div class="c_b_send" @click="send">
  104. <span></span>
  105. </div>
  106. </div>
  107. </div>
  108. </template>
  109. <script>
  110. import { v4 as uuidv4 } from "uuid";
  111. import MarkdownIt from "markdown-it";
  112. export default {
  113. data() {
  114. return {
  115. loading: false,
  116. chatLoading:false,
  117. userId:this.$route.query['userid'],
  118. cid: this.$route.query.cid,
  119. chatList: [],
  120. textValue: "",
  121. };
  122. },
  123. methods: {
  124. // 发送消息
  125. send() {
  126. if(this.chatLoading || this.loading)return this.$message.info("请稍等...")
  127. if(!this.textValue.trim())return this.$message.info("请输入内容");
  128. const _uuid = uuidv4();
  129. this.chatLoading = true;
  130. this.chatList.push({
  131. role: "user",
  132. content: `${this.textValue}`,
  133. uid: _uuid,
  134. AI: "AI",
  135. aiContent: "",
  136. oldContent: "",
  137. filename: "",
  138. index: this.chatList.length,
  139. createtime:new Date().toLocaleString().replaceAll("/",'-'),
  140. loading: true,
  141. })
  142. let history = [];
  143. let _msg = ''
  144. if(this.textValue=='作业思路讨论'){
  145. this,$message.info("作业思路讨论")
  146. _msg = `作业思路讨论`
  147. }else if(this.textValue=='回答学习疑惑'){
  148. this,$message.info("回答学习疑惑")
  149. _msg = `回答学习疑惑`
  150. }else if(this.textValue=='查找资源资料'){
  151. this,$message.info("查找资源资料")
  152. _msg = `查找资源资料`
  153. }else if(this.textValue=='整理学习资料'){
  154. this,$message.info("整理学习资料")
  155. _msg = `整理学习资料`
  156. }
  157. if(_msg){
  158. history.push({role:"user",content:_msg})
  159. }
  160. history.push({role:"user",content:this.textValue})
  161. // let params = {
  162. // model: "gpt-3.5-turbo",
  163. // temperature: 0,
  164. // max_tokens: 4096,
  165. // top_p: 1,
  166. // frequency_penalty: 0,
  167. // presence_penalty: 0,
  168. // messages: history,
  169. // uid: _uuid,
  170. // mind_map_question: this.textValue,
  171. // }
  172. let params = JSON.stringify({
  173. message: {
  174. anthropic_version: "bedrock-2023-05-31",
  175. max_tokens: 4096,
  176. temperature: 0,
  177. top_p: 1,
  178. messages: history
  179. },
  180. uid: _uuid,
  181. model: "Claude 3 Sonnet" // Claude 3 Sonnet或者Claude 3 Haiku
  182. });
  183. this.scrollBottom();
  184. this.textValue = "";
  185. this.ajax
  186. // .post("https://gpt4.cocorobo.cn/chat", params)
  187. .post("https://claude3.cocorobo.cn/chat", params)
  188. .then((res) => {
  189. if (res.data.FunctionResponse.result == "发送成功") {
  190. } else {
  191. this.chatLoading = false;
  192. this.$message.warning(res.data.FunctionResponse.result);
  193. }
  194. })
  195. .catch((e) => {
  196. this.chatLoading = false;
  197. console.log(e);
  198. });
  199. // this.$message.info(`发送:${this.textValue}`);
  200. // this.textValue = "";
  201. this.getAiContent(_uuid)
  202. },
  203. getAiContent(_uid) {
  204. let _source = new EventSource(`https://claude3.cocorobo.cn/streamChat/${_uid}`);
  205. // let _source = new EventSource(`https://gpt4.cocorobo.cn/stream/${_uid}`); //http://gpt4.cocorobo.cn:8011/stream/ https://gpt4.cocorobo.cn/stream/
  206. let _allText = "";
  207. let _mdText = "";
  208. const md = new MarkdownIt();
  209. _source.onmessage = (_e) => {
  210. if (_e.data.replace("'", "").replace("'", "") == "[DONE]") {
  211. //对话已经完成
  212. _mdText = _mdText.replace("_", "");
  213. _source.close();
  214. this.chatList.find((i) => i.uid == _uid).aiContent = _mdText;
  215. this.chatList.find((i) => i.uid == _uid).isalltext = true;
  216. this.chatList.find((i) => i.uid == _uid).isShowSynchronization = true;
  217. this.chatList.find((i) => i.uid == _uid).loading = false;
  218. this.chatLoading = false;
  219. this.insertChat(_uid);
  220. return;
  221. } else {
  222. //对话还在继续
  223. let _text = "";
  224. _text = _e.data.replaceAll("'", "");
  225. if (_allText == "") {
  226. _allText = _text.replace(/^\n+/, ""); //去掉回复消息中偶尔开头就存在的连续换行符
  227. } else {
  228. _allText += _text;
  229. }
  230. _mdText = _allText + "_";
  231. _mdText = _mdText.replace(/\\n/g, "\n");
  232. _mdText = _mdText.replace(/\\/g, "");
  233. if (_allText.split("```").length % 2 == 0) _mdText += "\n```\n";
  234. //转化返回的回复流数据
  235. _mdText = md.render(_mdText);
  236. this.chatList.find((i) => i.uid == _uid).aiContent = _mdText;
  237. this.chatList.find((i) => i.uid == _uid).loading = false;
  238. this.scrollBottom();
  239. // 处理流数据
  240. }
  241. };
  242. },
  243. // 复制
  244. copyText(_html) {
  245. const div = document.createElement("div")
  246. div.innerHTML = _html
  247. const _text = div.innerText
  248. navigator.clipboard.writeText(_text).then(_=>{
  249. this.$message.success("复制成功")
  250. }, function(err) {
  251. this.$message.error(`无法复制:${err}`)
  252. });
  253. },
  254. // 保存对话
  255. //保存消息
  256. insertChat(_uid) {
  257. let _data = this.chatList.find((i) => i.uid == _uid);
  258. if (!_data) return;
  259. let params = {
  260. userId: this.userId,
  261. userName: "qgt",
  262. groupId: "602def61-005d-11ee-91d8-005056b8q12w",
  263. answer: _data.aiContent,
  264. problem: _data.content,
  265. file_id: "",
  266. alltext: _data.aiContent,
  267. type: "chat",
  268. filename: _data.filename,
  269. session_name: `${this.userId}-${this.cid}-pblCourse`,
  270. };
  271. this.ajax
  272. .post("https://gpt4.cocorobo.cn/insert_chat", params)
  273. .then((res) => {
  274. console.log("保存对话")
  275. });
  276. },
  277. // 获取对话记录
  278. getChatList() {
  279. return new Promise((resolve, reject) => {
  280. this.chatList = [];
  281. this.loading = true;
  282. this.chatLoading = true;
  283. let params = {
  284. userid: this.userId,
  285. groupid: "602def61-005d-11ee-91d8-005056b8q12w",
  286. // session_name:``
  287. session_name: `${this.userId}-${this.cid}-pblCourse`,
  288. };
  289. this.ajax
  290. .post("https://gpt4.cocorobo.cn/get_agent_park_chat", params)
  291. .then((res) => {
  292. let _data = JSON.parse(res.data.FunctionResponse);
  293. if (_data.length > 0) {
  294. console.log(_data)
  295. let _chatList = [];
  296. for (let i = 0; i < _data.length; i++) {
  297. _chatList.push({
  298. loading: false,
  299. role: "user",
  300. content: _data[i].problem,
  301. uid: _data[i].id,
  302. AI: "AI",
  303. aiContent: _data[i].answer,
  304. oldContent: _data[i].answer,
  305. isShowSynchronization: false,
  306. filename: _data[i].filename,
  307. index: i,
  308. is_mind_map: false,
  309. fileid: _data[i].fileid,
  310. createtime:_data[i].createtime
  311. });
  312. }
  313. this.chatList = _chatList;
  314. this.chatLoading = false;
  315. this.loading = false;
  316. } else {
  317. //没有对话记录
  318. this.loading = false;
  319. this.chatLoading = false;
  320. }
  321. resolve();
  322. this.scrollBottom();
  323. })
  324. .catch((err) => {
  325. console.log(err);
  326. this.$message.error("获取对话记录失败");
  327. this.chatLoading = false;
  328. this.scrollBottom();
  329. resolve();
  330. });
  331. });
  332. },
  333. //对话触底
  334. scrollBottom(){
  335. this.$nextTick(()=>{
  336. this.$refs.chatRef.scrollTop = this.$refs.chatRef.scrollHeight;
  337. })
  338. },
  339. //清除对话
  340. clearChat(){
  341. this.chatList = [];
  342. this.scrollBottom();
  343. },
  344. sendChat(_text){
  345. this.textValue = _text;
  346. this.send();
  347. }
  348. },
  349. mounted() {
  350. this.getChatList().then(_=>{
  351. this.scrollBottom();
  352. })
  353. },
  354. };
  355. </script>
  356. <style scoped>
  357. .chat {
  358. width: 100%;
  359. height: 100%;
  360. background-color: #ffffff;
  361. border-radius: 12px;
  362. box-sizing: border-box;
  363. border: solid 1px #f3f7fd;
  364. box-shadow: 0 4px 10px 0 #1d388321;
  365. }
  366. .c_chat {
  367. width: 100%;
  368. height: calc(100% - 56px);
  369. box-sizing: border-box;
  370. padding: 15px;
  371. overflow: auto;
  372. }
  373. .c_c_item {
  374. background-color: none;
  375. }
  376. .c_c_i_user {
  377. width: 100%;
  378. height: auto;
  379. display: flex;
  380. justify-content: flex-end;
  381. align-items: flex-start;
  382. margin-bottom: 10px;
  383. }
  384. .c_c_i_u_message {
  385. height: auto;
  386. width: auto;
  387. max-width: calc(100% - 50px - 40px);
  388. display: flex;
  389. flex-direction: column;
  390. align-items: flex-end;
  391. margin-right: 5px;
  392. }
  393. .c_c_i_u_m_top {
  394. margin-bottom: 10px;
  395. display: flex;
  396. align-items: flex-end;
  397. }
  398. .c_c_i_u_m_top > span {
  399. margin-left: 5px;
  400. }
  401. .c_c_i_u_m_bottom {
  402. display: flex;
  403. align-items: flex-end;
  404. }
  405. .coptText {
  406. min-width: 15px;
  407. min-height: 15px;
  408. display: block;
  409. background: url("../../../../assets/icon/pblCourse/copyIcon.png") no-repeat;
  410. background-size: 100% 100%;
  411. margin: 0px 10px 6px 10px;
  412. cursor: pointer;
  413. }
  414. .c_c_i_u_m_content {
  415. width: auto;
  416. height: auto;
  417. padding: 10px;
  418. background-color: #e2eeff;
  419. border-radius: 8px 2px 8px 8px;
  420. border: solid 1px #000000e5;
  421. }
  422. .c_c_i_u_avatar {
  423. width: 45px;
  424. height: 45px;
  425. margin: 0px 0 0 10px;
  426. }
  427. .c_c_i_ai {
  428. margin-top: 10px;
  429. width: 100%;
  430. height: auto;
  431. display: flex;
  432. align-items: flex-start;
  433. margin-bottom: 10px;
  434. }
  435. .c_c_i_ai_avatar {
  436. width: 45px;
  437. height: 45px;
  438. margin: 0px 0 0 10px;
  439. }
  440. .c_c_i_ai_message {
  441. height: auto;
  442. width: auto;
  443. max-width: calc(100% - 50px - 40px);
  444. display: flex;
  445. flex-direction: column;
  446. align-items: flex-start;
  447. margin-left: 5px;
  448. }
  449. .c_c_i_ai_m_top {
  450. display: flex;
  451. align-items: flex-end;
  452. margin-bottom: 10px;
  453. }
  454. .c_c_i_ai_m_top > span {
  455. margin-left: 5px;
  456. }
  457. .c_c_i_ai_m_bottom {
  458. display: flex;
  459. align-items: flex-end;
  460. }
  461. .c_c_i_ai_m_content {
  462. min-width: 40px;
  463. min-height: 25px;
  464. width: auto;
  465. height: auto;
  466. padding: 10px;
  467. background-color: #ffffff;
  468. border-radius: 2px 8px 8px 8px;
  469. border: solid 1px #000000e5;
  470. }
  471. .chatContent >>> ol {
  472. margin-left: 25px;
  473. }
  474. .chatContent >>> ul {
  475. margin-left: 25px;
  476. }
  477. .chatTime{
  478. font-size: 14px;
  479. margin: 0 10px;
  480. color: #2c2f3b;
  481. }
  482. .c_controls{
  483. width: 100%;
  484. height: 30px;
  485. }
  486. .c_controls>span{
  487. max-height: 30px;
  488. width: auto;
  489. padding: 5px 10px;
  490. font-size: 14px;
  491. border-radius: 15px;
  492. box-shadow: 0 0 2px 0px gray;
  493. margin-left: 10px;
  494. cursor: pointer;
  495. }
  496. .c_controls>span:hover{
  497. background-color: #f3f7fd;
  498. }
  499. .c_bottom {
  500. width: 100%;
  501. height: 56px;
  502. display: flex;
  503. align-items: center;
  504. justify-content: center;
  505. box-sizing: border-box;
  506. border-top: solid 0.5px #e7e7e7;
  507. }
  508. .c_b_record {
  509. width: 30px;
  510. height: 30px;
  511. display: flex;
  512. justify-content: center;
  513. align-items: center;
  514. }
  515. .c_b_record > span {
  516. width: 24px;
  517. height: 24px;
  518. background-image: url("../../../../assets/icon/pblCourse/recordIcon.png");
  519. background-size: 100% 100%;
  520. display: flex;
  521. justify-content: center;
  522. align-items: center;
  523. }
  524. .c_b_inputArea {
  525. width: calc(100% - 100px);
  526. background-color: #f3f3f3;
  527. border-radius: 50px;
  528. height: 80%;
  529. display: flex;
  530. justify-content: center;
  531. align-items: center;
  532. margin: 0 15px;
  533. }
  534. .c_b_input {
  535. width: calc(100% - 40px);
  536. margin-right: 10px;
  537. }
  538. .c_b_input >>> .el-input__inner {
  539. border: none;
  540. background-color: #f3f3f3;
  541. outline: none;
  542. border-radius: 50px 0 0 50px;
  543. font-size: 1.2em;
  544. }
  545. .c_b_inputArea > span {
  546. width: 24px;
  547. height: 24px;
  548. background-image: url("../../../../assets/icon/pblCourse/fileIcon.png");
  549. background-size: 100% 100%;
  550. display: flex;
  551. justify-content: center;
  552. align-items: center;
  553. margin-right: 10px;
  554. cursor: pointer;
  555. }
  556. .c_b_send {
  557. width: 40px;
  558. height: 40px;
  559. border-radius: 50%;
  560. background-color: #3681fc;
  561. cursor: pointer;
  562. display: flex;
  563. justify-content: center;
  564. align-items: center;
  565. cursor: pointer;
  566. }
  567. .c_b_send > span {
  568. width: 60%;
  569. height: 60%;
  570. background-image: url("../../../../assets/icon/pblCourse/sendIcon.png");
  571. background-size: 100% 100%;
  572. }
  573. </style>