levitatedSphere.vue 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713
  1. <template>
  2. <div class="levitatedSphere" v-show="show">
  3. <div :class="['ls_hello']" v-show="showIndex == 0" @click="stopOne()" @dblclick="stopTwo()">
  4. <el-image
  5. style="width: 110%; height: 110%"
  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: 110%; height: 110%"
  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: 110%; height: 110%;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="iframeSrc"
  44. ref="iiframe"
  45. v-show="false"
  46. ></iframe>
  47. <!-- 文字转语音-->
  48. <iframe
  49. allow="camera *; microphone *;display-capture;midi;encrypted-media;"
  50. :src="iframeSrc1"
  51. ref="iiframe2"
  52. v-show="false"
  53. ></iframe>
  54. <mini-audio
  55. v-show="false"
  56. ref="audioRef"
  57. :loop="false"
  58. :audio-source="wozaiAudioUrl"
  59. ></mini-audio>
  60. </div>
  61. </template>
  62. <script>
  63. import { v4 as uuidv4 } from "uuid";
  64. import MarkdownIt from "markdown-it";
  65. import { fetchEventSource } from "@microsoft/fetch-event-source";
  66. export default {
  67. data() {
  68. return {
  69. show: false,
  70. showIndex: 0,
  71. aiStatus: 0, //0 :在说话 1 : 接收 2:待命
  72. aiText: "您好,我是小可,有什么可以帮助您的?",
  73. userText: "",
  74. showTextIndex: 0, //0:ai,1:用户, 2:组织语言 3: 无
  75. timer: null,
  76. isOpen: false,
  77. userId: this.$route.query.userid,
  78. chatLoading: false,
  79. talkLoading: false,
  80. source: null,
  81. talkTextList: [],
  82. wozaiAudioUrl:"https://ccrb.s3.cn-northwest-1.amazonaws.com.cn/default%2F%E4%B8%AD%E6%96%87%E7%89%88%E6%88%91%E5%9C%A81728961654002.wav",
  83. wozaiAudioEnglishUrl:"https://ccrb.s3.cn-northwest-1.amazonaws.com.cn/default%2F%E8%8B%B1%E6%96%87%E7%89%88what%27sup1728961667376.wav",
  84. };
  85. },
  86. computed: {
  87. iframeSrc() {
  88. if(this.$region == 'hk') {
  89. return `https://cloud.cocorobo.hk/browser/public/index.html`;
  90. } else if(this.$region == 'com') {
  91. return `https://cloud.cocorobo.com/browser/public/index.html`;
  92. } else {
  93. return `https://beta.cloud.cocorobo.cn/browser/public/index.html`;
  94. }
  95. },
  96. iframeSrc1() {
  97. if(this.$region == 'hk') {
  98. return `https://cloud.cocorobo.hk/browser/public/index1.html`;
  99. } else if(this.$region == 'com') {
  100. return `https://cloud.cocorobo.com/browser/public/index1.html`;
  101. } else {
  102. return `https://beta.cloud.cocorobo.cn/browser/public/index1.html`;
  103. }
  104. },
  105. htmlContent() {
  106. const md = new MarkdownIt();
  107. return _md => {
  108. return md.render(_md);
  109. };
  110. }
  111. },
  112. methods: {
  113. stopTwo(){
  114. this.show = false;
  115. this.showTextIndex = 3;
  116. this.aiStatus = 2;
  117. this.aiText = "";
  118. this.userText = "";
  119. this.stopRecord();
  120. if(this.curRequestController){
  121. this.curRequestController.abort();
  122. console.log("请求已终止");
  123. this.curRequestController = null;
  124. }
  125. },
  126. stopOne(){
  127. if(this.source){
  128. this.source.close();
  129. this.source = null;
  130. }
  131. if (this.talkLoading) {
  132. this.stopTalk();
  133. }
  134. if(this.curRequestController){
  135. this.curRequestController.abort();
  136. console.log("请求已终止");
  137. this.curRequestController = null;
  138. }
  139. this.showIndex = 0;
  140. this.showTextIndex = 0;
  141. this.aiText = "您好,我是小可,有什么可以帮助您的?";
  142. this.aiStatus = 0;
  143. this.chatLoading = false;
  144. },
  145. recordStart(_text) {
  146. var OpenCC = require("opencc-js");
  147. let converter = OpenCC.Converter({
  148. from: "hk",
  149. to: "cn"
  150. });
  151. // try {
  152. // this.$parent.changeRecordType(1);
  153. // return this.$message.success("已开启语音助手,请说“可可同学”来唤醒")
  154. if (this.isOpen)
  155. return this.$message.info("已开启语音助手,无需重复开启");
  156. let iiframe = this.$refs["iiframe"];
  157. iiframe.contentWindow.window.document.getElementById(
  158. "languageOptions"
  159. ).selectedIndex = 2; //普通话
  160. iiframe.contentWindow.testdoContinuousPronunciationAssessment();
  161. //this.talkTextList.push("我在");
  162. //this.talkText();
  163. // console.log("打开")
  164. this.$refs.audioRef.play();
  165. // return;
  166. // this.$message.success("已开启语音助手,请说“可可同学”来唤醒");
  167. this.$parent.changeRecordType(1);
  168. this.isOpen = true;
  169. this.aiText = "您好,我是小可,有什么可以帮助您的?";
  170. this.aiStatus = 0;
  171. this.showIndex = 0;
  172. this.showTextIndex = 0;
  173. this.chatLoading = false;
  174. this.talkLoading == false;
  175. this.show = true;
  176. iiframe.contentWindow.onRecognizedResult = e => {
  177. let _msg = converter(e.privText);
  178. // let _msg2 = e.privText;
  179. // let _msg = "你好呀!";
  180. console.log("👇");
  181. console.log(_msg);
  182. // _msg = converter(_msg)
  183. if (!_msg || _msg == '.') return console.log("输出为空");
  184. // if (_msg.indexOf(converter("可可同学")) != -1 && !this.show) {
  185. // this.aiText = "您好,我是小可,有什么可以帮助您的?";
  186. // this.aiStatus = 0;
  187. // this.showIndex = 0;
  188. // this.show = true;
  189. // console.log("已唤醒");
  190. // return;
  191. // } else
  192. if (this.show == true) {
  193. if (
  194. _msg.indexOf(converter("可可同学")) != -1 &&
  195. _msg.indexOf(converter("停止")) != -1
  196. ) {
  197. this.stopTalk();
  198. } else if (
  199. this.showTextIndex == 2 ||
  200. this.chatLoading ||
  201. this.talkLoading
  202. ) {
  203. return console.log("组织语言中");
  204. // }else if(_msg.indexOf('可可同学')!=-1 && _msg.indexOf("停止")!=-1){
  205. // this.stopTalk();
  206. } else {
  207. this.showTextIndex = 1;
  208. this.aiText = "";
  209. this.userText += _msg;
  210. this.aiStatus = 1;
  211. if (this.timer) {
  212. clearTimeout(this.timer);
  213. this.timer = null;
  214. }
  215. this.timer = setTimeout(() => {
  216. if (this.userText.indexOf(converter("关闭语音助手")) != -1) {
  217. // return setTimeout(()=>{
  218. this.show = false;
  219. this.showTextIndex = 3;
  220. this.aiStatus = 2;
  221. this.aiText = "";
  222. this.userText = "";
  223. this.stopRecord();
  224. return;
  225. // },1000)
  226. }
  227. this.showTextIndex = 2;
  228. this.aiText = "";
  229. let regExp = new RegExp(
  230. converter("计时") + "(.+)" + converter("分钟")
  231. );
  232. if (regExp.test(this.userText)) {
  233. // setTimeout(() => {
  234. let _number = this.userText.match(regExp)[1];
  235. let _time = 0;
  236. if (!/^\d+$/.test(_number)) {
  237. _time = this.chineseToNumber(_number) * 60;
  238. } else {
  239. _time = parseInt(_numberList[1]) * 60;
  240. }
  241. this.$emit("startTime", _time);
  242. this.aiStatus = 0;
  243. this.showTextIndex = 0;
  244. this.aiText =
  245. "好的,我已为您计时" +
  246. this.userText.match(regExp)[1] +
  247. "分钟。";
  248. this.userText = "";
  249. this.timer = setTimeout(() => {
  250. this.showTextIndex = 3;
  251. this.aiStatus = 2;
  252. this.aiText = "";
  253. this.userText = "";
  254. }, 3000);
  255. // }, 2000);
  256. } else {
  257. let msgList = [];
  258. msgList.push({
  259. role:"user",
  260. content: `请在150字以内回答以下问题:\n${this.userText}`
  261. })
  262. this.chatLoading = true;
  263. const _uuid = uuidv4();
  264. let params = {
  265. model: "gpt-4o-2024-11-20",
  266. temperature: 0,
  267. max_tokens: 4096,
  268. top_p: 1,
  269. frequency_penalty: 0,
  270. presence_penalty: 0,
  271. messages: msgList,
  272. uid: this.userId,
  273. mind_map_question: "",
  274. stream:true
  275. };
  276. const md = new MarkdownIt();
  277. let _this = this;
  278. this.curRequestController = new AbortController();
  279. let _allText = "";
  280. let _mdText = "";
  281. let _talkText = "";
  282. fetchEventSource("https://gpt4.cocorobo.cn/chat_post_stream",{
  283. method: "POST",
  284. headers: {
  285. "Content-Type": "application/json"
  286. },
  287. body: JSON.stringify(params),
  288. signal: _this.curRequestController.signal,
  289. onmessage(_e){
  290. console.log('_e',_e)
  291. _this.showIndex = 0;
  292. if(!_e.data)return
  293. let _eData = _e.data;
  294. if (_eData.replace("'", "").replace("'", "") == "[DONE]") {
  295. // this.source.close();
  296. // this.source = null;
  297. // let _result = [];
  298. // if ("result" in _eData) {
  299. // _result = _eData.result;
  300. // for (let i = 0; i < _result.length; i++) {
  301. // _mdText = _mdText.replace(_result[i].text, _result[i].fileName);
  302. // }
  303. // }
  304. _mdText = _mdText.replace("_", "");
  305. _this.aiText = _mdText;
  306. // _this.scrollBottom();
  307. if (_talkText != "") {
  308. let _resultText = _this.removeMarkdown(_talkText);
  309. _this.talkTextList.push(_resultText);
  310. _talkText = "";
  311. if (!_this.talkLoading) _this.talkText();
  312. }
  313. _this.chatLoading = false;
  314. _this.userText = '';
  315. } else {
  316. // _talkIndex+=1;
  317. let _text = _eData.replace("'", "").replace("'", "");
  318. if (_allText == "") {
  319. _allText = _text.replace(/^\n+/, ""); //去掉回复消息中偶尔开头就存在的连续换行符
  320. _talkText += _text.replace(/^\n+/, "");
  321. } else {
  322. _allText += _text;
  323. _talkText += _text;
  324. }
  325. `~`;
  326. _mdText = _allText + "_";
  327. _mdText = _mdText.replace(/\\n/g, "\n");
  328. _mdText = _mdText.replace(/\\/g, "");
  329. if (_allText.split("```").length % 2 == 0) _mdText += "\n```\n";
  330. _this.aiText = _mdText;
  331. _this.showTextIndex = 0;
  332. // _this.scrollBottom();
  333. if (/[,。:;?!)]/.test(_talkText)) {
  334. let _resultText = _this.removeMarkdown(_talkText);
  335. _this.talkTextList.push(_resultText);
  336. _talkText = "";
  337. if (!_this.talkLoading) _this.talkText();
  338. }
  339. }
  340. // let _data = ev.data;
  341. // if(_data=='[DONE]')return;
  342. // _addText+=_data;
  343. // this.aiText = md.render(_addText)
  344. // // _this.chatList.find(i => i.uid == _uuid).aiContent = md.render(_addText);
  345. // // _this.chatList.find(i => i.uid == _uuid).loading = false;
  346. // _this.scrollBottom();
  347. // console.log(_data)
  348. },
  349. onclose(){
  350. _this.$forceUpdate();
  351. _this.userText = '';
  352. // _this.stopTalkToken = null;
  353. // // _this.faloading = false;
  354. _this.curRequestController = null;
  355. // _this.chatList.find(i => i.uid == _uuid).isalltext = true;
  356. // _this.chatList.find(i => i.uid == _uuid).isShowSynchronization = true;
  357. // _this.insertChat(_uuid);
  358. console.log("连接关闭")
  359. },
  360. onerror(err){
  361. console.log("连接错误",err)
  362. }
  363. })
  364. // this.ajax
  365. // // .post("https://claude3.cocorobo.cn/chat", params)
  366. // // .post("https://gpt4.cocorobo.cn/chat", params)
  367. // .post(
  368. // "https://gpt4.cocorobo.cn/ai_agent_park_chat_new",
  369. // params
  370. // )
  371. // .then(res => {
  372. // if (
  373. // converter(res.data.FunctionResponse.result) ==
  374. // converter("发送成功")
  375. // ) {
  376. // this.userText = "";
  377. // this.showIndex = 0;
  378. // } else {
  379. // // this.$message.warning(res.data.FunctionResponse.result);
  380. // console.log(res.data.FunctionResponse.result);
  381. // this.chatLoading = false;
  382. // this.aiStatus = 0;
  383. // this.showTextIndex = 0;
  384. // this.aiText = "对不起,我无法理解您的问题,请重新提问";
  385. // this.timer = setTimeout(() => {
  386. // this.showTextIndex = 3;
  387. // this.aiStatus = 2;
  388. // this.aiText = "";
  389. // this.userText = "";
  390. // }, 3000);
  391. // }
  392. // })
  393. // .catch(e => {
  394. // console.log(e);
  395. // this.chatLoading = false;
  396. // this.aiStatus = 0;
  397. // this.showTextIndex = 0;
  398. // this.aiText = "对不起,我无法理解您的问题,请重新提问";
  399. // this.timer = setTimeout(() => {
  400. // this.showTextIndex = 3;
  401. // this.aiStatus = 2;
  402. // this.aiText = "";
  403. // this.userText = "";
  404. // }, 3000);
  405. // });
  406. // // 通过流获取ai对话数据
  407. // this.getAtAuContent(_uuid);
  408. }
  409. }, 5000);
  410. }
  411. } else {
  412. console.log("不响应");
  413. }
  414. };
  415. // } catch (error) {
  416. // console.log("ai录音报错👇");
  417. // console.log(error);
  418. // setTimeout(() => {
  419. // this.recordStart();
  420. // }, 1000);
  421. // }
  422. },
  423. stopRecord() {
  424. // this.$parent.changeRecordType(0);
  425. // this.$message.success("已关闭语音助手")
  426. // return
  427. try {
  428. let iiframe = this.$refs["iiframe"];
  429. iiframe.contentWindow.window.document
  430. .getElementById("scenarioStopButton")
  431. .click();
  432. // this.stopTalk();
  433. // 录音借宿
  434. iiframe.contentWindow.onSessionStopped = (s, e) => {
  435. this.isOpen = false;
  436. this.show = false;
  437. this.showTextIndex = 3;
  438. this.showIndex = 2;
  439. this.$parent.changeRecordType(0);
  440. this.$message.success("已关闭语音助手");
  441. if (this.talkLoading) {
  442. this.stopTalk();
  443. }
  444. this.userText = "";
  445. this.aiText = "";
  446. };
  447. } catch (error) {
  448. console.log(error)
  449. this.isOpen = false;
  450. this.show = false;
  451. this.showTextIndex = 3;
  452. this.showIndex = 2;
  453. this.$parent.changeRecordType(0);
  454. this.$message.success("已关闭语音助手");
  455. this.userText = "";
  456. this.aiText = "";
  457. }
  458. },
  459. chineseToNumber(chinese) {
  460. var OpenCC = require("opencc-js");
  461. let converter = OpenCC.Converter({
  462. from: "hk",
  463. to: "cn"
  464. });
  465. chinese = converter(chinese);
  466. const chineseNumbers = {
  467. 零: 0,
  468. 一: 1,
  469. 二: 2,
  470. 三: 3,
  471. 四: 4,
  472. 五: 5,
  473. 六: 6,
  474. 七: 7,
  475. 八: 8,
  476. 九: 9,
  477. 十: 10,
  478. 百: 100,
  479. 千: 1000,
  480. 万: 10000,
  481. 亿: 100000000
  482. };
  483. let result = 0;
  484. let tempNum = 0; // 用于累积处理
  485. let sectionNum = 0; // 每个段的值
  486. for (let i = 0; i < chinese.length; i++) {
  487. const char = chinese[i];
  488. const num = chineseNumbers[char];
  489. if (num === undefined) {
  490. throw new Error(`Unexpected character: ${char}`);
  491. }
  492. if (
  493. num === 10 ||
  494. num === 100 ||
  495. num === 1000 ||
  496. num === 10000 ||
  497. num === 100000000
  498. ) {
  499. if (tempNum === 0) tempNum = 1; // 如果前面没有数,默认是1
  500. tempNum *= num;
  501. if (num === 10000 || num === 100000000) {
  502. sectionNum += tempNum;
  503. result += sectionNum;
  504. tempNum = 0;
  505. sectionNum = 0;
  506. }
  507. } else {
  508. tempNum += num;
  509. }
  510. }
  511. result += sectionNum + tempNum;
  512. return result;
  513. },
  514. removeMarkdown(text) {
  515. return text
  516. .replace(/[#*_~`>+\-]/g, "") // 移除 #、*、_、~、`、>、+、- 符号
  517. .replace(/!\[.*?\]\(.*?\)/g, "") // 移除图片
  518. .replace(/\[.*?\]\(.*?\)/g, "") // 移除链接
  519. .replace(/```[\s\S]*?```/g, "") // 移除代码块(不使用 s 标志)
  520. .replace(/`[^`]*`/g, "") // 移除行内代码
  521. .replace(/\d+\./g, "") // 移除有序列表
  522. .replace(/^\s*[-*+]\s+/gm, "") // 移除无序列表
  523. .replace(/\s+/g, " ") // 将多个空白字符替换为一个空格
  524. .trim(); // 去除字符串两端的空白字符
  525. },
  526. getAtAuContent(_uid) {
  527. this.source = new EventSource(
  528. `https://gpt4.cocorobo.cn/question/${_uid}`
  529. );
  530. //http://gpt4.cocorobo.cn:8011/question/ https://gpt4.cocorobo.cn/question/
  531. let _allText = "";
  532. let _mdText = "";
  533. let _talkText = "";
  534. // let _talkIndex = 0;
  535. // const md = new MarkdownIt();
  536. this.source.onmessage = _e => {
  537. this.showIndex = 0;
  538. this.aiStatus = 0
  539. let _eData = JSON.parse(_e.data);
  540. if (_eData.content.replace("'", "").replace("'", "") == "[DONE]") {
  541. let _result = [];
  542. if ("result" in _eData) {
  543. _result = _eData.result;
  544. for (let i = 0; i < _result.length; i++) {
  545. _mdText = _mdText.replace(_result[i].text, _result[i].fileName);
  546. }
  547. }
  548. _mdText = _mdText.replace("_", "");
  549. this.aiText = _mdText;
  550. if (_talkText != "") {
  551. let _resultText = this.removeMarkdown(_talkText);
  552. this.talkTextList.push(_resultText);
  553. _talkText = "";
  554. if (!this.talkLoading) this.talkText();
  555. }
  556. this.chatLoading = false;
  557. this.source.close();
  558. } else {
  559. // _talkIndex+=1;
  560. let _text = _eData.content.replace("'", "").replace("'", "");
  561. if (_allText == "") {
  562. _allText = _text.replace(/^\n+/, ""); //去掉回复消息中偶尔开头就存在的连续换行符
  563. _talkText += _text.replace(/^\n+/, "");
  564. } else {
  565. _allText += _text;
  566. _talkText += _text;
  567. }
  568. `~`;
  569. _mdText = _allText + "_";
  570. _mdText = _mdText.replace(/\\n/g, "\n");
  571. _mdText = _mdText.replace(/\\/g, "");
  572. if (_allText.split("```").length % 2 == 0) _mdText += "\n```\n";
  573. this.aiText = _mdText;
  574. this.showTextIndex = 0;
  575. if (/[,。:;?!)]/.test(_talkText)) {
  576. let _resultText = this.removeMarkdown(_talkText);
  577. this.talkTextList.push(_resultText);
  578. _talkText = "";
  579. if (!this.talkLoading) this.talkText();
  580. }
  581. }
  582. };
  583. },
  584. talkText() {
  585. let _text = this.talkTextList.shift();
  586. let _talkTextIiframe2 = this.$refs.iiframe2;
  587. if (_text) {
  588. this.talkLoading = true;
  589. if (this.timer) {
  590. clearTimeout(this.timer);
  591. this.timer = null;
  592. }
  593. console.log(`👉转语音:${_text}`);
  594. _talkTextIiframe2.contentWindow.texttospeech(
  595. _text,
  596. this.talkText,
  597. this.endTalk
  598. );
  599. } else {
  600. _talkTextIiframe2.contentWindow.closesynthesizer();
  601. }
  602. },
  603. endTalk() {
  604. console.log("👉转语音结束👈");
  605. this.talkLoading = false;
  606. },
  607. stopTalk() {
  608. this.talkTextList = [];
  609. let _talkTextIiframe2 = this.$refs.iiframe2;
  610. _talkTextIiframe2.contentWindow.closesynthesizer();
  611. _talkTextIiframe2.contentWindow.pausesynthesizer();
  612. this.talkLoading = false;
  613. }
  614. },
  615. mounted() {
  616. // // this.recordStart()
  617. // setTimeout(()=>{
  618. // this.recordStart("可可同学。")
  619. // setTimeout(()=>{
  620. // this.recordStart("世界上最大的火山是什么。")
  621. // setTimeout(()=>{
  622. // this.recordStart("位于哪里。他温度怎么样。")
  623. // },2000)
  624. // },2000)
  625. // },5000)
  626. }
  627. };
  628. </script>
  629. <style scoped>
  630. .levitatedSphere {
  631. position: fixed;
  632. width: auto;
  633. height: auto;
  634. top: 20px;
  635. right: 10px;
  636. z-index: 9999;
  637. display: flex;
  638. }
  639. .ls_hello {
  640. width: 80px;
  641. height: 80px;
  642. position: absolute;
  643. top: 0;
  644. right: 0;
  645. cursor: pointer;
  646. animation: smallToBig 1s both;
  647. }
  648. /* 定义渐入动画 */
  649. @keyframes smallToBig {
  650. from {
  651. opacity: 0;
  652. transform: scale(0.1);
  653. }
  654. to {
  655. opacity: 1;
  656. transform: scale(1);
  657. }
  658. }
  659. .ls_text {
  660. position: absolute;
  661. right: 100px;
  662. top: 20px;
  663. max-width: 300px;
  664. display: flex;
  665. justify-content: flex-end;
  666. }
  667. .ls_t_ai {
  668. padding: 12px;
  669. width: auto;
  670. height: auto;
  671. border-radius: 12px 0px 12px 12px;
  672. background: #00000099;
  673. color: #fff;
  674. }
  675. .ls_t_user {
  676. padding: 12px;
  677. width: auto;
  678. height: auto;
  679. border-radius: 12px 12px 12px 12px;
  680. background: #00000099;
  681. color: #fff;
  682. }
  683. </style>