index.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536
  1. <template>
  2. <div class="ch_box" ref="ch_box">
  3. <div class="ch_content_box" v-if="type == 1">
  4. <searchArea
  5. :courseDetail="courseDetail"
  6. ref="searchAreaRef"
  7. :navList="navList"
  8. :tcid="tcid"
  9. :openMegaphone="openMegaphone"
  10. :fileId="fileId"
  11. :recordType="recordType"
  12. v-if="itemType == 1"
  13. />
  14. <taskArea
  15. :courseDetail="courseDetail"
  16. :navList="navList"
  17. :courseType="courseType"
  18. :taskCount="taskCount"
  19. :worksStudent="worksStudent"
  20. :openMegaphone="openMegaphone"
  21. ref="taskAreaRef"
  22. :fileId="fileId"
  23. v-if="itemType == 2"
  24. />
  25. <dialogArea
  26. :courseDetail="courseDetail"
  27. :openMegaphone="openMegaphone"
  28. ref="dialogAreaRef"
  29. :fileId="fileId"
  30. v-if="itemType == 3"
  31. />
  32. </div>
  33. <div class="ch_nav_box">
  34. <div class="ch_nav_box_top">
  35. <div @click="changeFold(!fold)">
  36. <el-tooltip class="item" effect="dark" :content="fold?'折叠':'展开'" placement="top">
  37. <img
  38. :src="require('../../assets/icon/course/foldIcon.svg')"
  39. alt=""
  40. :style="`${fold?'transform: rotate(90deg);':''}`"
  41. />
  42. </el-tooltip>
  43. <div v-if="fold" class="itemFold">
  44. <div @click="$emit('backPage')">
  45. <el-tooltip
  46. class="item"
  47. effect="dark"
  48. content="返回"
  49. placement="top"
  50. >
  51. <img
  52. :src="require('../../assets/icon/course/return.png')"
  53. alt=""
  54. />
  55. </el-tooltip>
  56. </div>
  57. <div @click="$emit('refresh')">
  58. <el-tooltip
  59. class="item"
  60. effect="dark"
  61. content="刷新"
  62. placement="top"
  63. >
  64. <img
  65. :src="require('../../assets/icon/course/refresh.png')"
  66. alt=""
  67. />
  68. </el-tooltip>
  69. </div>
  70. <div @click="$emit('authority')" v-if="tType == 1 || tType == 4">
  71. <el-tooltip
  72. class="item"
  73. effect="dark"
  74. content="权限"
  75. placement="top"
  76. >
  77. <img
  78. :src="require('../../assets/icon/course/setting.png')"
  79. alt=""
  80. />
  81. </el-tooltip>
  82. </div>
  83. </div>
  84. </div>
  85. <div @click="$emit('review')" v-if="tType == 1">
  86. <el-tooltip class="item" effect="dark" content="评论" placement="top">
  87. <img
  88. :src="require('../../assets/icon/course/comment.png')"
  89. alt=""
  90. style="width: 22px;height: 22px;"
  91. />
  92. </el-tooltip>
  93. </div>
  94. <div @click="$emit('startRecording')" v-if="!videoStart && (tType == 1 || tType == 4)">
  95. <el-tooltip class="item" effect="dark" content="录制" placement="top">
  96. <img
  97. :src="require('../../assets/icon/course/record.svg')"
  98. alt=""
  99. style="width: 22px;height: 22px;"
  100. />
  101. </el-tooltip>
  102. </div>
  103. <div @click="$emit('stopRecording')" v-else-if="(tType == 1 || tType == 4)" style="background:#f63564">
  104. <el-tooltip class="item" effect="dark" content="下载" placement="top">
  105. <img
  106. :src="require('../../assets/icon/course/record2.svg')"
  107. alt=""
  108. style="width: 22px;height: 22px;"
  109. />
  110. </el-tooltip>
  111. </div>
  112. </div>
  113. <div class="ch_nav_box_middle">
  114. <div
  115. :class="[
  116. 'ch_nav_box_middle_item',
  117. itemType == 2 ? 'ch_nav_box_middle_item_active' : ''
  118. ]"
  119. @click.stop="changeItemType(2)"
  120. >
  121. <img
  122. v-if="itemType == 2"
  123. :src="require('../../assets/icon/course/task_active.png')"
  124. />
  125. <img
  126. v-if="itemType != 2"
  127. :src="require('../../assets/icon/course/task.png')"
  128. />
  129. <!-- <span :style="`background:url(${itemType==2?require('../../assets/icon/course/task_active.png'):require('../../assets/icon/course/task.png')});`"></span> -->
  130. <div>任务</div>
  131. </div>
  132. <div
  133. :class="[
  134. 'ch_nav_box_middle_item',
  135. itemType == 1 ? 'ch_nav_box_middle_item_active' : ''
  136. ]"
  137. @click.stop="changeItemType(1)"
  138. >
  139. <img
  140. v-if="itemType == 1"
  141. :src="require('../../assets/icon/course/up_active.png')"
  142. />
  143. <img
  144. v-if="itemType != 1"
  145. :src="require('../../assets/icon/course/up.png')"
  146. />
  147. <!-- <span :style="`background:url(${itemType==1?require('../../assets/icon/course/up_active.png'):require('../../assets/icon/course/up.png')});`"></span> -->
  148. <div>对话</div>
  149. </div>
  150. <div
  151. :class="[
  152. 'ch_nav_box_middle_item',
  153. itemType == 3 ? 'ch_nav_box_middle_item_active' : ''
  154. ]"
  155. @click.stop="changeItemType(3)"
  156. >
  157. <img
  158. v-if="itemType == 3"
  159. :src="require('../../assets/icon/course/dialog_active.png')"
  160. />
  161. <img
  162. v-if="itemType != 3"
  163. :src="require('../../assets/icon/course/dialog.png')"
  164. />
  165. <!-- <span :style="`background:url(${itemType==3?require('../../assets/icon/course/dialog_active.png'):require('../../assets/icon/course/dialog.png')});`"></span> -->
  166. <div>智能体</div>
  167. </div>
  168. </div>
  169. <div class="ch_nav_box_bottom">
  170. <div @click.stop="startAssistant()" :class="[recordType==1?'ch_nav_box_middle_item_active':'']">
  171. <el-tooltip
  172. class="item"
  173. effect="dark"
  174. :content="recordType==0?'开启语音助手':'关闭语音助手'"
  175. placement="top"
  176. >
  177. <img v-if="recordType!=1" :src="require('../../assets/icon/course/robot.svg')" />
  178. <img v-else :src="require('../../assets/icon/course/robot2.svg')" />
  179. </el-tooltip>
  180. </div>
  181. <div @click.stop="$emit('goStep', 0)">
  182. <el-tooltip
  183. class="item"
  184. effect="dark"
  185. content="上一步"
  186. placement="top"
  187. >
  188. <img :src="require('../../assets/icon/course/last.png')" />
  189. </el-tooltip>
  190. </div>
  191. <div @click.stop="$emit('goStep', 1)">
  192. <el-tooltip
  193. class="item"
  194. effect="dark"
  195. content="下一步"
  196. placement="top"
  197. >
  198. <img :src="require('../../assets/icon/course/next.png')" />
  199. </el-tooltip>
  200. </div>
  201. <div @click="openSetting">
  202. <el-tooltip
  203. class="item"
  204. effect="dark"
  205. :content="type == 0 ? '展开' : '折叠'"
  206. placement="top"
  207. >
  208. <img :src="require('../../assets/icon/course/menu.png')" />
  209. </el-tooltip>
  210. </div>
  211. </div>
  212. </div>
  213. <levitatedSphere ref="levitatedSphereRef" @startTime="startTime" />
  214. <timepiece ref="timepieceRef" />
  215. </div>
  216. </template>
  217. <script>
  218. import searchArea from "./component/searchArea.vue";
  219. import taskArea from "./component/taskArea.vue";
  220. import dialogArea from "./component/dialogArea.vue";
  221. import levitatedSphere from "./component/levitatedSphere.vue";
  222. import timepiece from "./component/timepiece.vue";
  223. export default {
  224. emits: ["refresh", "goStep", "backPage", "authority", "review","stopRecording","startRecording"],
  225. components: {
  226. searchArea,
  227. taskArea,
  228. dialogArea,
  229. levitatedSphere,
  230. timepiece
  231. },
  232. props: {
  233. courseDetail: {
  234. type: Object,
  235. default: () => {}
  236. },
  237. tType: {
  238. type: Number,
  239. default: 0
  240. },
  241. navList: {
  242. type: Array,
  243. default: () => []
  244. },
  245. tcid: {
  246. type: String,
  247. default: ""
  248. },
  249. courseType: {
  250. type: Number,
  251. default: 0
  252. },
  253. taskCount: {
  254. type: Number,
  255. default: 0
  256. },
  257. worksStudent: {
  258. type: Array,
  259. default: () => []
  260. },
  261. fileList: {
  262. type: Array,
  263. default: () => []
  264. },
  265. videoStart:{
  266. type:Boolean,
  267. default:false,
  268. },
  269. },
  270. data() {
  271. return {
  272. type: 0,
  273. itemType: 0, //0--无 1-搜索 2-任务 3-对话
  274. fileId: [],
  275. recordType: 0,
  276. recordLoading:false,
  277. fold: false,
  278. openMegaphone:false,//是否打开喇叭
  279. getFileIdLoading: false
  280. };
  281. },
  282. mounted() {
  283. this.setWidth();
  284. this.getFileId();
  285. },
  286. methods: {
  287. setWidth() {
  288. let w = this.$refs.ch_box;
  289. let w2 = w.offsetWidth + 30 + "px";
  290. this.$emit("setWidth", w2);
  291. },
  292. openSetting() {
  293. this.type = this.type == 1 ? 0 : 1;
  294. this.$nextTick(() => {
  295. this.setWidth();
  296. });
  297. },
  298. changeItemType(type) {
  299. this.type = 0;
  300. this.openSetting();
  301. this.$nextTick(() => {
  302. // if (this.itemType == 1 && type != 1) {
  303. // this.$refs.searchAreaRef.scrollBottom();
  304. // this.$refs.searchAreaRef.getWantSearch();
  305. // } else if (this.itemType == 2) {
  306. // this.$refs.taskAreaRef.scrollBottom();
  307. // } else if (this.itemType == 3) {
  308. // this.$refs.dialogAreaRef.scrollBottom();
  309. // }
  310. this.itemType = type;
  311. });
  312. },
  313. //计时
  314. startTime(time) {
  315. this.$refs.timepieceRef.startTime(time);
  316. },
  317. getFileId() {
  318. if (this.getFileIdLoading) return;
  319. this.getFileIdLoading = true;
  320. this.fileId = [];
  321. let _this = this;
  322. let _successFileUrl = [];
  323. if (this.fileList.length <= 0) retrun;
  324. let addType = ["DOCX", "DOC", "PPT", "PPTX", "MD", "TXT", "PDF"];
  325. this.fileList.forEach(i => {
  326. if (
  327. addType.includes(
  328. i.url.split(".")[i.url.split(".").length - 1].toLocaleUpperCase()
  329. )
  330. ) {
  331. _successFileUrl.push(i.url);
  332. }
  333. });
  334. let promiseList = [];
  335. _successFileUrl.forEach(i => {
  336. promiseList.push(
  337. new Promise((resolve, reject) => {
  338. _this.ajax
  339. .put("https://gpt4.cocorobo.cn/upload_file_knowledge", {
  340. url: i
  341. })
  342. .then(res => {
  343. let _data = res.data.FunctionResponse;
  344. if (_data.result && _data.result.id) {
  345. this.fileId.push(_data.result.id);
  346. }
  347. resolve();
  348. });
  349. })
  350. );
  351. });
  352. Promise.all(promiseList).then(res => {
  353. this.getFileIdLoading = false;
  354. });
  355. },
  356. startAssistant() {
  357. if(this.recordLoading)return this.$message.info("请稍等...")
  358. this.recordLoading = true;
  359. if (this.recordType == 0) {
  360. // this.$message.info("开启")
  361. // this.changeRecordType(1)
  362. this.$refs.levitatedSphereRef.recordStart();
  363. } else if (this.recordType == 1) {
  364. // this.$message.info("关闭")
  365. // this.changeRecordType(0)
  366. this.$refs.levitatedSphereRef.stopRecord();
  367. }
  368. },
  369. changeMegaphone(){
  370. this.openMegaphone = !this.openMegaphone;
  371. if(this.openMegaphone){
  372. this.$message.success("已开启AI语音")
  373. }else{
  374. this.$message.success("已关闭AI语音")
  375. }
  376. },
  377. // 展开
  378. changeFold(newValue) {
  379. // this.$message.info("展开");
  380. this.fold = newValue;
  381. },
  382. changeRecordType(type) {
  383. this.recordLoading = false;
  384. this.recordType = type;
  385. }
  386. }
  387. };
  388. </script>
  389. <style scoped>
  390. .ch_box {
  391. width: auto;
  392. background: rgb(255, 255, 255);
  393. position: fixed;
  394. height: calc(100% - 40px);
  395. border-radius: 10px;
  396. box-sizing: border-box;
  397. right: 20px;
  398. display: flex;
  399. top: 20px;
  400. z-index: 1000;
  401. }
  402. .ch_nav_box {
  403. height: 100%;
  404. width: 65px;
  405. display: flex;
  406. flex-direction: column;
  407. align-items: center;
  408. /* overflow-y: auto; */
  409. overflow-x: visible; /* 确保横向溢出内容可见 */
  410. position: relative;
  411. }
  412. .ch_content_box {
  413. width: 400px;
  414. height: 100%;
  415. border-right: 2px solid #e7e7e7;
  416. }
  417. .ch_nav_box_bottom {
  418. width: 100%;
  419. box-sizing: border-box;
  420. border-top: solid 1px #eaeaea;
  421. display: flex;
  422. flex-direction: column;
  423. }
  424. .ch_nav_box_middle {
  425. width: 100%;
  426. box-sizing: border-box;
  427. border-top: solid 1px #eaeaea;
  428. flex-direction: column;
  429. justify-content: space-between;
  430. }
  431. .ch_nav_box_middle_item {
  432. width: 100%;
  433. height: 80px;
  434. display: flex;
  435. flex-direction: column;
  436. justify-content: center;
  437. align-items: center;
  438. cursor: pointer;
  439. transition: 0.3s;
  440. font-size: 14px;
  441. }
  442. .ch_nav_box_middle_item_active {
  443. background-color: #3681fc;
  444. color: white;
  445. }
  446. .ch_nav_box_middle_item > img {
  447. width: 24px;
  448. height: 24px;
  449. margin-bottom: 5px;
  450. }
  451. .ch_nav_box_bottom > div {
  452. width: 100%;
  453. height: 65px;
  454. display: flex;
  455. flex-direction: column;
  456. justify-content: center;
  457. align-items: center;
  458. cursor: pointer;
  459. }
  460. .ch_nav_box_bottom > div > img {
  461. width: 24px;
  462. height: 24px;
  463. }
  464. .ch_nav_box_top {
  465. width: 100%;
  466. margin-top: auto;
  467. }
  468. .ch_nav_box_top > div {
  469. width: 100%;
  470. height: 65px;
  471. display: flex;
  472. justify-content: center;
  473. align-items: center;
  474. cursor: pointer;
  475. position: relative;
  476. }
  477. .ch_nav_box_top > div > img {
  478. width: 24px;
  479. height: 24px;
  480. transition: .3s;
  481. }
  482. .itemFold {
  483. position: absolute;
  484. width: 65px;
  485. left:-65px;
  486. top: 0;
  487. background: rgb(255, 255, 255);
  488. box-sizing: border-box;
  489. border: solid 1px #eaeaea;
  490. /* border-top: solid 1px #eaeaea; */
  491. z-index: 1000; /* 确保二级菜单在主菜单上层 */
  492. border-radius: 10px;
  493. }
  494. .itemFold > div {
  495. width: 100%;
  496. height: 65px;
  497. display: flex;
  498. justify-content: center;
  499. align-items: center;
  500. cursor: pointer;
  501. }
  502. .itemFold > div > img {
  503. width: 24px;
  504. height: 24px;
  505. }
  506. </style>