levitatedSphere.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516
  1. <template>
  2. <div class="levitatedSphere" v-show="show">
  3. <div :class="['ls_hello']" v-show="showIndex == 0">
  4. <el-image
  5. style="width: 100%; height: 100%"
  6. v-show="aiStatus == 1"
  7. :src="require('../../../assets/icon/course/aiTalk.svg')"
  8. fit="fill"
  9. ></el-image>
  10. <el-image
  11. style="width: 100%; height: 100%"
  12. v-show="aiStatus == 2"
  13. :src="require('../../../assets/icon/course/aiVanish.svg')"
  14. fit="fill"
  15. ></el-image>
  16. <el-image
  17. style="width: 100%; height: 100%;transform: scale(1.3,1.3);"
  18. v-show="aiStatus == 0"
  19. :src="require('../../../assets/icon/course/aiWait2.svg')"
  20. fit="fill"
  21. ></el-image>
  22. </div>
  23. <div
  24. class="ls_text"
  25. :style="{
  26. width: userText || aiText || showTextIndex == 2 ? '300px' : '0px'
  27. }"
  28. >
  29. <div class="ls_t_ai" v-if="[0, 2].includes(showTextIndex)">
  30. <span
  31. v-if="[0].includes(showTextIndex)"
  32. v-html="htmlContent(aiText)"
  33. ></span>
  34. <span v-if="[2].includes(showTextIndex)">正在组织语言...</span>
  35. </div>
  36. <div class="ls_t_user" v-if="[1].includes(showTextIndex)">
  37. {{ userText }}
  38. </div>
  39. </div>
  40. <!-- 录音转文字 -->
  41. <iframe
  42. allow="camera *; microphone *;display-capture;midi;encrypted-media;"
  43. src="https://beta.cloud.cocorobo.cn/browser/public/index.html"
  44. ref="iiframe"
  45. v-show="false"
  46. ></iframe>
  47. <!-- 文字转语音-->
  48. <iframe
  49. allow="camera *; microphone *;display-capture;midi;encrypted-media;"
  50. src="https://beta.cloud.cocorobo.cn/browser/public/index1.html"
  51. ref="iiframe2"
  52. v-show="false"
  53. ></iframe>
  54. </div>
  55. </template>
  56. <script>
  57. import { v4 as uuidv4 } from "uuid";
  58. import MarkdownIt from "markdown-it";
  59. export default {
  60. data() {
  61. return {
  62. show: false,
  63. showIndex: 0, //0 :在说话 1 : 接收 2:待命
  64. aiStatus: 0,
  65. aiText: "您好,我是小可,有什么可以帮助您的?",
  66. userText: "",
  67. showTextIndex: 0, //0:ai,1:用户, 2:组织语言 3: 无
  68. timer: null,
  69. isOpen: false,
  70. userId: this.$route.query.userid,
  71. chatLoading: false,
  72. talkLoading: false,
  73. source: null,
  74. talkTextList: []
  75. };
  76. },
  77. computed: {
  78. htmlContent() {
  79. const md = new MarkdownIt();
  80. return _md => {
  81. return md.render(_md);
  82. };
  83. }
  84. },
  85. methods: {
  86. recordStart(_text) {
  87. var OpenCC = require("opencc-js");
  88. let converter = OpenCC.Converter({
  89. from: "hk",
  90. to: "cn"
  91. });
  92. // try {
  93. // this.$parent.changeRecordType(1);
  94. // return this.$message.success("已开启语音助手,请说“可可同学”来唤醒")
  95. if (this.isOpen)
  96. return this.$message.info("已开启语音助手,无需重复开启");
  97. let iiframe = this.$refs["iiframe"];
  98. iiframe.contentWindow.window.document.getElementById(
  99. "languageOptions"
  100. ).selectedIndex = 2; //普通话
  101. iiframe.contentWindow.testdoContinuousPronunciationAssessment();
  102. this.$message.success("已开启语音助手,请说“可可同学”来唤醒");
  103. this.$parent.changeRecordType(1);
  104. this.isOpen = true;
  105. iiframe.contentWindow.onRecognizedResult = e => {
  106. let _msg = converter(e.privText);
  107. // let _msg2 = e.privText;
  108. // let _msg = _text;
  109. console.log("👇");
  110. console.log(_msg);
  111. // _msg = converter(_msg)
  112. if (!_msg) return console.log("输出为空");
  113. if (_msg.indexOf(converter("可可同学")) != -1 && !this.show) {
  114. this.aiText = "您好,我是小可,有什么可以帮助您的?";
  115. this.aiStatus = 0;
  116. this.showIndex = 0;
  117. this.show = true;
  118. console.log("已唤醒");
  119. return;
  120. } else if (this.show == true) {
  121. if (
  122. _msg.indexOf(converter("可可同学")) != -1 &&
  123. _msg.indexOf(converter("停止")) != -1
  124. ) {
  125. this.stopTalk();
  126. } else if (
  127. this.showTextIndex == 2 ||
  128. this.chatLoading ||
  129. this.talkLoading
  130. ) {
  131. return console.log("组织语言中");
  132. // }else if(_msg.indexOf('可可同学')!=-1 && _msg.indexOf("停止")!=-1){
  133. // this.stopTalk();
  134. } else {
  135. this.showTextIndex = 1;
  136. this.aiText = "";
  137. this.userText += _msg;
  138. this.aiStatus = 1;
  139. if (this.timer) {
  140. clearTimeout(this.timer);
  141. this.timer = null;
  142. }
  143. this.timer = setTimeout(() => {
  144. if (this.userText.indexOf(converter("关闭语音助手")) != -1) {
  145. // return setTimeout(()=>{
  146. this.show = false;
  147. this.showTextIndex = 3;
  148. this.aiStatus = 2;
  149. this.aiText = "";
  150. this.userText = "";
  151. this.stopRecord();
  152. return;
  153. // },1000)
  154. }
  155. this.showTextIndex = 2;
  156. this.aiText = "";
  157. let regExp = new RegExp(
  158. converter("计时") + "(.+)" + converter("分钟")
  159. );
  160. if (regExp.test(this.userText)) {
  161. // setTimeout(() => {
  162. let _number = this.userText.match(regExp)[1];
  163. let _time = 0;
  164. if (!/^\d+$/.test(_number)) {
  165. _time = this.chineseToNumber(_number) * 60;
  166. } else {
  167. _time = parseInt(_numberList[1]) * 60;
  168. }
  169. this.$emit("startTime", _time);
  170. this.aiStatus = 0;
  171. this.showTextIndex = 0;
  172. this.aiText =
  173. "好的,我已为您计时" +
  174. this.userText.match(regExp)[1] +
  175. "分钟。";
  176. this.userText = "";
  177. this.timer = setTimeout(() => {
  178. this.showTextIndex = 3;
  179. this.aiStatus = 2;
  180. this.aiText = "";
  181. this.userText = "";
  182. }, 3000);
  183. // }, 2000);
  184. } else {
  185. this.chatLoading = true;
  186. const _uuid = uuidv4();
  187. let params = {
  188. assistant_id: "f8e1ebb2-2e0d-11ef-8bf4-12e77c4cb76b",
  189. userId: this.userId,
  190. message: this.userText,
  191. session_name: _uuid + "-qgt",
  192. uid: _uuid,
  193. file_ids: []
  194. };
  195. this.ajax
  196. // .post("https://claude3.cocorobo.cn/chat", params)
  197. // .post("https://gpt4.cocorobo.cn/chat", params)
  198. .post(
  199. "https://gpt4.cocorobo.cn/ai_agent_park_chat_new",
  200. params
  201. )
  202. .then(res => {
  203. if (
  204. converter(res.data.FunctionResponse.result) ==
  205. converter("发送成功")
  206. ) {
  207. this.userText = "";
  208. } else {
  209. // this.$message.warning(res.data.FunctionResponse.result);
  210. console.log(res.data.FunctionResponse.result);
  211. this.chatLoading = false;
  212. this.aiStatus = 0;
  213. this.showTextIndex = 0;
  214. this.aiText = "对不起,我无法理解您的问题,请重新提问";
  215. this.timer = setTimeout(() => {
  216. this.showTextIndex = 3;
  217. this.aiStatus = 2;
  218. this.aiText = "";
  219. this.userText = "";
  220. }, 3000);
  221. }
  222. })
  223. .catch(e => {
  224. console.log(e);
  225. this.chatLoading = false;
  226. this.aiStatus = 0;
  227. this.showTextIndex = 0;
  228. this.aiText = "对不起,我无法理解您的问题,请重新提问";
  229. this.timer = setTimeout(() => {
  230. this.showTextIndex = 3;
  231. this.aiStatus = 2;
  232. this.aiText = "";
  233. this.userText = "";
  234. }, 3000);
  235. });
  236. // 通过流获取ai对话数据
  237. this.getAtAuContent(_uuid);
  238. }
  239. }, 5000);
  240. }
  241. } else {
  242. console.log("不响应");
  243. }
  244. };
  245. // } catch (error) {
  246. // console.log("ai录音报错👇");
  247. // console.log(error);
  248. // setTimeout(() => {
  249. // this.recordStart();
  250. // }, 1000);
  251. // }
  252. },
  253. stopRecord() {
  254. // this.$parent.changeRecordType(0);
  255. // this.$message.success("已关闭语音助手")
  256. // return
  257. let iiframe = this.$refs["iiframe"];
  258. iiframe.contentWindow.window.document
  259. .getElementById("scenarioStopButton")
  260. .click();
  261. if (this.talkLoading) {
  262. this.stopTalk();
  263. }
  264. // this.stopTalk();
  265. // 录音借宿
  266. iiframe.contentWindow.onSessionStopped = (s, e) => {
  267. this.isOpen = false;
  268. this.show = false;
  269. this.showTextIndex = 3;
  270. this.showIndex = 2;
  271. this.$parent.changeRecordType(0);
  272. this.$message.success("已关闭语音助手");
  273. if (this.talkLoading) {
  274. this.$refs.iiframe2.contentWindow.closesynthesizer();
  275. }
  276. this.userText = "";
  277. this.aiText = "";
  278. };
  279. },
  280. chineseToNumber(chinese) {
  281. var OpenCC = require("opencc-js");
  282. let converter = OpenCC.Converter({
  283. from: "hk",
  284. to: "cn"
  285. });
  286. chinese = converter(chinese);
  287. const chineseNumbers = {
  288. 零: 0,
  289. 一: 1,
  290. 二: 2,
  291. 三: 3,
  292. 四: 4,
  293. 五: 5,
  294. 六: 6,
  295. 七: 7,
  296. 八: 8,
  297. 九: 9,
  298. 十: 10,
  299. 百: 100,
  300. 千: 1000,
  301. 万: 10000,
  302. 亿: 100000000
  303. };
  304. let result = 0;
  305. let tempNum = 0; // 用于累积处理
  306. let sectionNum = 0; // 每个段的值
  307. for (let i = 0; i < chinese.length; i++) {
  308. const char = chinese[i];
  309. const num = chineseNumbers[char];
  310. if (num === undefined) {
  311. throw new Error(`Unexpected character: ${char}`);
  312. }
  313. if (
  314. num === 10 ||
  315. num === 100 ||
  316. num === 1000 ||
  317. num === 10000 ||
  318. num === 100000000
  319. ) {
  320. if (tempNum === 0) tempNum = 1; // 如果前面没有数,默认是1
  321. tempNum *= num;
  322. if (num === 10000 || num === 100000000) {
  323. sectionNum += tempNum;
  324. result += sectionNum;
  325. tempNum = 0;
  326. sectionNum = 0;
  327. }
  328. } else {
  329. tempNum += num;
  330. }
  331. }
  332. result += sectionNum + tempNum;
  333. return result;
  334. },
  335. removeMarkdown(text) {
  336. return text
  337. .replace(/[#*_~`>+\-]/g, "") // 移除 #、*、_、~、`、>、+、- 符号
  338. .replace(/!\[.*?\]\(.*?\)/g, "") // 移除图片
  339. .replace(/\[.*?\]\(.*?\)/g, "") // 移除链接
  340. .replace(/```[\s\S]*?```/g, "") // 移除代码块(不使用 s 标志)
  341. .replace(/`[^`]*`/g, "") // 移除行内代码
  342. .replace(/\d+\./g, "") // 移除有序列表
  343. .replace(/^\s*[-*+]\s+/gm, "") // 移除无序列表
  344. .replace(/\s+/g, " ") // 将多个空白字符替换为一个空格
  345. .trim(); // 去除字符串两端的空白字符
  346. },
  347. getAtAuContent(_uid) {
  348. this.source = new EventSource(
  349. `https://gpt4.cocorobo.cn/question/${_uid}`
  350. );
  351. //http://gpt4.cocorobo.cn:8011/question/ https://gpt4.cocorobo.cn/question/
  352. let _allText = "";
  353. let _mdText = "";
  354. let _talkText = "";
  355. // let _talkIndex = 0;
  356. // const md = new MarkdownIt();
  357. this.source.onmessage = _e => {
  358. let _eData = JSON.parse(_e.data);
  359. if (_eData.content.replace("'", "").replace("'", "") == "[DONE]") {
  360. let _result = [];
  361. if ("result" in _eData) {
  362. _result = _eData.result;
  363. for (let i = 0; i < _result.length; i++) {
  364. _mdText = _mdText.replace(_result[i].text, _result[i].fileName);
  365. }
  366. }
  367. _mdText = _mdText.replace("_", "");
  368. this.aiText = _mdText;
  369. if (_talkText != "") {
  370. let _resultText = this.removeMarkdown(_talkText);
  371. this.talkTextList.push(_resultText);
  372. _talkText = "";
  373. if (!this.talkLoading) this.talkText();
  374. }
  375. this.chatLoading = false;
  376. this.source.close();
  377. } else {
  378. // _talkIndex+=1;
  379. let _text = _eData.content.replace("'", "").replace("'", "");
  380. if (_allText == "") {
  381. _allText = _text.replace(/^\n+/, ""); //去掉回复消息中偶尔开头就存在的连续换行符
  382. _talkText += _text.replace(/^\n+/, "");
  383. } else {
  384. _allText += _text;
  385. _talkText += _text;
  386. }
  387. `~`;
  388. _mdText = _allText + "_";
  389. _mdText = _mdText.replace(/\\n/g, "\n");
  390. _mdText = _mdText.replace(/\\/g, "");
  391. if (_allText.split("```").length % 2 == 0) _mdText += "\n```\n";
  392. this.aiText = _mdText;
  393. this.showTextIndex = 0;
  394. if (/[,。:;?!)]/.test(_talkText)) {
  395. let _resultText = this.removeMarkdown(_talkText);
  396. this.talkTextList.push(_resultText);
  397. _talkText = "";
  398. if (!this.talkLoading) this.talkText();
  399. }
  400. }
  401. };
  402. },
  403. talkText() {
  404. let _text = this.talkTextList.shift();
  405. let _talkTextIiframe2 = this.$refs.iiframe2;
  406. if (_text) {
  407. this.talkLoading = true;
  408. if (this.timer) {
  409. clearTimeout(this.timer);
  410. this.timer = null;
  411. }
  412. console.log(`👉转语音:${_text}`);
  413. _talkTextIiframe2.contentWindow.texttospeech(
  414. _text,
  415. this.talkText,
  416. this.endTalk
  417. );
  418. } else {
  419. _talkTextIiframe2.contentWindow.closesynthesizer();
  420. }
  421. },
  422. endTalk() {
  423. console.log("👉转语音结束👈");
  424. this.talkLoading = false;
  425. },
  426. stopTalk() {
  427. this.talkTextList = [];
  428. let _talkTextIiframe2 = this.$refs.iiframe2;
  429. _talkTextIiframe2.contentWindow.closesynthesizer();
  430. _talkTextIiframe2.contentWindow.pausesynthesizer();
  431. this.talkLoading = false;
  432. }
  433. },
  434. mounted() {
  435. // // this.recordStart()
  436. // setTimeout(()=>{
  437. // this.recordStart("可可同学。")
  438. // setTimeout(()=>{
  439. // this.recordStart("世界上最大的火山是什么。")
  440. // setTimeout(()=>{
  441. // this.recordStart("位于哪里。他温度怎么样。")
  442. // },2000)
  443. // },2000)
  444. // },5000)
  445. }
  446. };
  447. </script>
  448. <style scoped>
  449. .levitatedSphere {
  450. position: fixed;
  451. width: auto;
  452. height: auto;
  453. top: 20px;
  454. right: 10px;
  455. z-index: 9999;
  456. display: flex;
  457. }
  458. .ls_hello {
  459. width: 80px;
  460. height: 80px;
  461. position: absolute;
  462. top: 0;
  463. right: 0;
  464. animation: smallToBig 1s both;
  465. }
  466. /* 定义渐入动画 */
  467. @keyframes smallToBig {
  468. from {
  469. opacity: 0;
  470. transform: scale(0.1);
  471. }
  472. to {
  473. opacity: 1;
  474. transform: scale(1);
  475. }
  476. }
  477. .ls_text {
  478. position: absolute;
  479. right: 100px;
  480. top: 20px;
  481. max-width: 300px;
  482. display: flex;
  483. justify-content: flex-end;
  484. }
  485. .ls_t_ai {
  486. padding: 12px;
  487. width: auto;
  488. height: auto;
  489. border-radius: 12px 0px 12px 12px;
  490. background: #00000099;
  491. color: #fff;
  492. }
  493. .ls_t_user {
  494. padding: 12px;
  495. width: auto;
  496. height: auto;
  497. border-radius: 12px 12px 12px 12px;
  498. background: #00000099;
  499. color: #fff;
  500. }
  501. </style>