AIPainting.vue 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619
  1. <template>
  2. <!-- 智能绘画 -->
  3. <div class="home-container">
  4. <!-- 展开收起侧边栏 -->
  5. <div
  6. class="icon-expand"
  7. :style="{
  8. backgroundColor: drawerVisible ? '#44449c' : '#7F70C840',
  9. left: drawerVisible ? '18%' : '0'
  10. }"
  11. @click="toggleDrawer"
  12. >
  13. <span
  14. class="vertical-lines"
  15. :style="{
  16. color: drawerVisible ? '#8a78d0' : 'white'
  17. }"
  18. >||</span
  19. >
  20. </div>
  21. <!-- 左侧折叠面板 -->
  22. <LeftPanel ref="leftPanelRef" v-if="drawerVisible"/>
  23. <div class="left-group2">
  24. <div class="title-box">
  25. <div class="box-icon" @click="goBack">
  26. <el-icon class="left-icon"><ArrowLeftBold /></el-icon>
  27. 智能绘画
  28. </div>
  29. </div>
  30. <div class="img-box">
  31. <p>
  32. <img
  33. style=" width: fit-content; height: 180px; margin: 10px;"
  34. src="@/assets/images/color.png"
  35. class="avatar user"
  36. />
  37. </p>
  38. <p>期待你的画作喔~</p>
  39. </div>
  40. </div>
  41. <!-- 右侧AI问答 -->
  42. <div class="number-people">
  43. <div class="content-box">
  44. <!-- AI对话框 -->
  45. <div class="chat-dialog">
  46. <!-- 对话消息列表 -->
  47. <div class="message-list">
  48. <div v-if="imageAllList.length > 0" v-for="(item, index) in imageAllList" :key="index">
  49. <!-- 用户消息 -->
  50. <div class="user-message" v-if="item.type === 'user'">
  51. {{ item.content }}
  52. </div>
  53. <!-- AI生成图片对话框 -->
  54. <div class="ai-message" v-if="item.type !== 'user'">
  55. {{ item.content }}
  56. <div class="image-list" v-if="item.imageList">
  57. <el-image
  58. v-for="(image, index) in item.imageList"
  59. :key="index"
  60. style=" width: fit-content; height: 220px; margin: 10px;"
  61. :src="image"
  62. :preview-src-list="item.imageList"
  63. fit="cover"
  64. show-progress
  65. >
  66. <template
  67. #toolbar="{ actions, prev, next, reset, activeIndex, setActiveItem }"
  68. >
  69. <el-icon @click="prev"><Back /></el-icon>
  70. <el-icon @click="next"><Right /></el-icon>
  71. <el-icon @click="setActiveItem(item.imageList.length - 1)">
  72. <DArrowRight />
  73. </el-icon>
  74. <el-icon @click="actions('zoomOut')"><ZoomOut /></el-icon>
  75. <el-icon
  76. @click="actions('zoomIn', { enableTransition: false, zoomRate: 2 })">
  77. <ZoomIn />
  78. </el-icon>
  79. <el-icon
  80. @click="actions('clockwise', { rotateDeg: 180, enableTransition: false })">
  81. <RefreshRight />
  82. </el-icon>
  83. <el-icon @click="actions('anticlockwise')"><RefreshLeft /></el-icon>
  84. <el-icon @click="reset"><Refresh /></el-icon>
  85. <el-icon @click="download(activeIndex)"><Download /></el-icon>
  86. </template>
  87. </el-image>
  88. </div>
  89. </div>
  90. </div>
  91. <div v-else class="content-demo">
  92. <h3>请参考示例:</h3>
  93. <!-- 用户消息 -->
  94. <div class="user-message">
  95. 生成粉色的会飞的猪
  96. </div>
  97. <!-- AI生成图片对话框 -->
  98. <div class="ai-message" >
  99. 为您生成图片:
  100. <div class="image-list" v-if="demoImageList">
  101. <el-image
  102. v-for="(image, index) in demoImageList"
  103. :key="index"
  104. style=" width: fit-content; height: 180px; margin: 10px;"
  105. :src="image"
  106. :preview-src-list="demoImageList"
  107. fit="cover"
  108. show-progress
  109. >
  110. <template
  111. #toolbar="{ actions, prev, next, reset, activeIndex, setActiveItem }"
  112. >
  113. <el-icon @click="prev"><Back /></el-icon>
  114. <el-icon @click="next"><Right /></el-icon>
  115. <el-icon @click="setActiveItem(demoImageList.length - 1)">
  116. <DArrowRight />
  117. </el-icon>
  118. <el-icon @click="actions('zoomOut')"><ZoomOut /></el-icon>
  119. <el-icon
  120. @click="actions('zoomIn', { enableTransition: false, zoomRate: 2 })">
  121. <ZoomIn />
  122. </el-icon>
  123. <el-icon
  124. @click="actions('clockwise', { rotateDeg: 180, enableTransition: false })">
  125. <RefreshRight />
  126. </el-icon>
  127. <el-icon @click="actions('anticlockwise')"><RefreshLeft /></el-icon>
  128. <el-icon @click="reset"><Refresh /></el-icon>
  129. <el-icon @click="download(activeIndex)"><Download /></el-icon>
  130. </template>
  131. </el-image>
  132. </div>
  133. </div>
  134. </div>
  135. </div>
  136. <!-- 输入框和发送按钮 -->
  137. <div class="input-section">
  138. <input
  139. type="text"
  140. v-model="inputMessage"
  141. placeholder="描述任何画面..."
  142. @keyup.enter="sendMessage"
  143. />
  144. <button @click="sendMessage">发送</button>
  145. </div>
  146. </div>
  147. </div>
  148. </div>
  149. </div>
  150. </template>
  151. <script setup>
  152. import { ref, onMounted,onUnmounted} from 'vue'
  153. import {AiImageStatusEnum, CreatePainting, PaintingGetMys} from '@/api/questions.js'
  154. import { useRouter, useRoute } from 'vue-router'
  155. import demo1 from '@/assets/images/ai-demo/ai-image-demo1.png'
  156. import demo2 from '@/assets/images/ai-demo/ai-image-demo2.png'
  157. import demo3 from '@/assets/images/ai-demo/ai-image-demo3.png'
  158. import demo4 from '@/assets/images/ai-demo/ai-image-demo4.png'
  159. import NumberPeople00 from '@/assets/images/xiaozhi.png'
  160. import {
  161. Document,
  162. Menu as IconMenu,
  163. Location,
  164. Setting,
  165. ArrowLeftBold,
  166. Fold,
  167. Expand,
  168. ChatLineRound,
  169. Picture,
  170. MagicStick,
  171. Tickets,
  172. User
  173. } from '@element-plus/icons-vue'
  174. import { saveRecord } from '@/api/personalized/index.js'
  175. // 导入全局状态
  176. import { globalState } from '@/utils/globalState.js'
  177. // 返回上一页
  178. const goBack = () => {
  179. router.push('/ai-laboratory')
  180. }
  181. const router = useRouter()
  182. const route = useRoute()
  183. // 导入图片
  184. import question from '@/assets/icon/question.png'
  185. import painting from '@/assets/icon/painting.png'
  186. import human from '@/assets/icon/human.png'
  187. import LeftPanel from '@/components/LeftPanel.vue'
  188. const leftPanelRef = ref(null)
  189. // 添加抽屉显示状态
  190. const drawerVisible = ref(true)
  191. // 添加切换抽屉显示状态的函数
  192. const toggleDrawer = () => {
  193. drawerVisible.value = !drawerVisible.value
  194. }
  195. // 跳转智能问答
  196. const navigateToAI = (group) => {
  197. if (group.title === "智能问答") {
  198. let person = { id: 10, name: '小智', image: NumberPeople00, message: '您好,我是您的AI智能助手小智,我会尽力回答您的问题或提供有用的建议!!!!' };
  199. router.push({
  200. // 跳转问答页面
  201. path: '/ai-questions',
  202. query: { id: person.id, name: person.name, image: person.image, message: person.message }
  203. });
  204. }
  205. if (group.title === "智能绘画") {
  206. router.push('/ai-painting')
  207. }
  208. if (group.title === '数字人老师') {
  209. router.push('/ai-laboratory') // 添加跳转到AI实验室的逻辑
  210. }
  211. }
  212. // 渲染侧边栏
  213. const groupList = ref([
  214. {
  215. icon: question,
  216. title: '智能问答'
  217. },
  218. {
  219. icon: painting,
  220. title: '智能绘画'
  221. },
  222. {
  223. icon: human,
  224. title: '数字人老师'
  225. }
  226. ])
  227. // 处理菜单展开和关闭事件
  228. const handleOpen = () => {}
  229. const handleClose = () => {}
  230. const demoImageList = [demo1, demo2, demo3, demo4]
  231. // 年级ID相关
  232. const gradeId = ref('')
  233. // 保存记录
  234. onMounted(async () => {
  235. // 从全局状态初始化年级ID
  236. gradeId.value = globalState.initGradeId()
  237. console.log(gradeId.value);
  238. try{
  239. const res = await saveRecord({
  240. brpNjId: gradeId.value,
  241. brpType: "aiCount",
  242. brpProgress: 1
  243. });
  244. console.log(res);
  245. }catch(error){
  246. console.error('保存记录失败:', error);
  247. }
  248. });
  249. // 消息列表和输入内容的响应式变量
  250. const messages = ref([])
  251. const inputMessage = ref('')
  252. // 发送消息函数
  253. const sendMessage = () => {
  254. console.log(inputMessage.value)
  255. if (inputMessage.value.trim()) {
  256. // messages.value.push(inputMessage.value.trim())
  257. // 先保存内容 再置空输入框
  258. let content = inputMessage.value;
  259. inputMessage.value = ''
  260. imageAllList.value.push({
  261. type: 'user',
  262. content: content,
  263. })
  264. imageAllList.value.push({
  265. type: 'ai',
  266. content: "正在为您生成图片,请稍等...",
  267. })
  268. CreatePainting({
  269. "modelId": 56,
  270. "prompt":content,
  271. "width":1024,
  272. "height":1024
  273. }).then(res=>{
  274. console.log("生成图片",res)
  275. //目前写死调用已生成的图片,全部通了后再改
  276. inProgressImageMap.value[res.data] = {id:res.data,status:AiImageStatusEnum.IN_PROGRESS}
  277. // inProgressImageMap.value[260] = {id:260,status:AiImageStatusEnum.IN_PROGRESS}
  278. })
  279. }
  280. }
  281. // 生成图片
  282. import { ElIcon } from 'element-plus'
  283. import {
  284. Back,
  285. DArrowRight,
  286. Download,
  287. Refresh,
  288. RefreshLeft,
  289. RefreshRight,
  290. Right,
  291. ZoomIn,
  292. ZoomOut,
  293. } from '@element-plus/icons-vue'
  294. const imageAllList = ref([]) // 对话的消息列表
  295. const imageList = ref([]) // image 列表
  296. // 图片轮询相关的参数(正在生成中的)
  297. const inProgressImageMap = ref({}) // 监听的 image 映射,一般是生成中(需要轮询),key 为 image 编号,value 为 image
  298. const inProgressTimer = ref() // 生成中的 image 定时器,轮询生成进展
  299. /** 轮询生成中的 image 列表 */
  300. const refreshWatchImages = async () => {
  301. const imageIds = Object.keys(inProgressImageMap.value).map(Number)
  302. if (imageIds.length === 0) {
  303. return
  304. }
  305. const list = await PaintingGetMys(imageIds)
  306. const newWatchImages = {}
  307. list.data.forEach((image) => {
  308. if (image.status === AiImageStatusEnum.IN_PROGRESS) {
  309. newWatchImages[image.id] = image
  310. } else {
  311. imageAllList.value.pop();
  312. imageAllList.value.push({
  313. type: 'ai',
  314. content: "已为您生成图片:",
  315. imageList: [image.picUrl],
  316. })
  317. }
  318. })
  319. inProgressImageMap.value = newWatchImages
  320. if (newWatchImages.size === 0) {
  321. inProgressTimerFun()
  322. }
  323. }
  324. /** 组件挂在的时候 */
  325. onMounted(async () => {
  326. refreshWatchImagesFun()
  327. })
  328. /** 组件取消挂在的时候 */
  329. onUnmounted(async () => {
  330. inProgressTimerFun()
  331. })
  332. // 自动刷新 image 列表
  333. const refreshWatchImagesFun = () => {
  334. inProgressTimer.value = setInterval(async () => {
  335. await refreshWatchImages()
  336. }, 1000 * 3)
  337. }
  338. // 停止刷新image列表
  339. const inProgressTimerFun = () => {
  340. if (inProgressTimer.value) {
  341. clearInterval(inProgressTimer.value)
  342. }
  343. }
  344. </script>
  345. <style scoped lang="scss">
  346. @use 'sass:math';
  347. // 定义rpx转换函数
  348. @function rpx($px) {
  349. @return math.div($px, 750) * 100vw;
  350. }
  351. /* 添加过渡样式 */
  352. .drawer-slide-enter-active,
  353. .drawer-slide-leave-active {
  354. transition: all 0.3s ease;
  355. }
  356. .drawer-slide-enter-from,
  357. .drawer-slide-leave-to {
  358. transform: translateX(-100%);
  359. opacity: 0;
  360. }
  361. .icon-expand {
  362. width: rpx(8);
  363. height: rpx(35);
  364. border-top-right-radius: rpx(5);
  365. border-bottom-right-radius: rpx(5);
  366. z-index: 9999;
  367. position: absolute;
  368. top: 50%;
  369. left: 18%;
  370. transform: translateY(-50%);
  371. background-color: #44449c;
  372. cursor: pointer; // 添加鼠标指针样式
  373. clip-path: polygon(0 0, 100% 15%, 100% 85%, 0 100%);
  374. display: flex;
  375. justify-content: center;
  376. align-items: center;
  377. transition: all 0.3s ease;
  378. }
  379. .icon-expand .vertical-lines {
  380. color: #8a78d0;
  381. font-size: rpx(10);
  382. }
  383. .menu-icon {
  384. width:rpx(11);
  385. height: rpx(11);
  386. margin-right: rpx(2);
  387. }
  388. // 侧边栏
  389. .left-group1 {
  390. width: rpx(135);
  391. height: 100%;
  392. background: linear-gradient(to bottom, #001169, #8a78d0);
  393. }
  394. .home-container {
  395. position: fixed;
  396. top: 0;
  397. left: 0;
  398. right: 0;
  399. bottom: 0;
  400. display: flex;
  401. flex-direction: row;
  402. gap: rpx(0);
  403. background: linear-gradient(
  404. to bottom,
  405. #e2ddfc,
  406. #f1effd
  407. ); /* 设置悬停、聚焦、点击状态下的背景色 */
  408. }
  409. // 侧边栏
  410. .left-group {
  411. width: rpx(135);
  412. height: 100%;
  413. background: linear-gradient(to bottom, #001169, #b4a8e1);
  414. }
  415. .mb-2 {
  416. color: black;
  417. margin-top: rpx(1);
  418. }
  419. .tac ::v-deep(.el-menu) {
  420. background-color: transparent;
  421. border: none;
  422. width: 100%;
  423. margin-top: rpx(55);
  424. margin-left: rpx(10);
  425. }
  426. .el-menu-item {
  427. width: rpx(115);
  428. height: rpx(25);
  429. margin-bottom: rpx(5);
  430. border-radius: rpx(6);
  431. color: white;
  432. font-size: rpx(8);
  433. }
  434. .el-menu-item .el-icon svg {
  435. font-size: rpx(15);
  436. color: white;
  437. }
  438. .el-menu ::v-deep(.el-menu-item:hover),
  439. .el-menu ::v-deep(.el-menu-item:focus),
  440. .el-menu ::v-deep(.el-menu-item:active) {
  441. background: linear-gradient(
  442. to bottom,
  443. #ffefb0,
  444. #ffcc00
  445. ); /* 设置悬停、聚焦、点击状态下的背景色 */
  446. box-shadow: 0 8px 8px rgb(0, 0, 0, 0.3);
  447. color: black;
  448. font-size: rpx(8);
  449. }
  450. .el-menu-vertical-demo .el-menu-item.is-active {
  451. /* 可根据需求修改选中样式 */
  452. background: linear-gradient(
  453. to bottom,
  454. #ffefb0,
  455. #ffcc00
  456. ); /* 设置悬停、聚焦、点击状态下的背景色 */
  457. box-shadow: 0 8px 8px rgb(0, 0, 0, 0.3);
  458. color: black;
  459. font-size: rpx(8);
  460. }
  461. // 侧边栏
  462. .left-group2 {
  463. width: rpx(150);
  464. height: 100%;
  465. background-color: #ece9fd;
  466. }
  467. .left-group2 img {
  468. width: rpx(110);
  469. height: auto;
  470. margin-top: rpx(30);
  471. }
  472. .title-box {
  473. height: rpx(50);
  474. }
  475. .box-icon {
  476. width: 100%;
  477. height: 100%;
  478. flex: 1;
  479. display: flex; // 添加 flex 布局
  480. align-items: center; // 垂直居中
  481. color: black; // 设置图标颜色为白色
  482. padding-left: rpx(15);
  483. font-size: rpx(10); // 设置图标大小,可按需调整
  484. cursor: pointer; // 添加鼠标指针样式
  485. }
  486. .box-icon .left-icon {
  487. margin-left: rpx(10);
  488. margin-right: rpx(5); // 设置图标和文字之间的间距 ;
  489. }
  490. .number-people {
  491. flex: 1;
  492. height: 100%;
  493. display: flex;
  494. background-color: #ece9fd;
  495. }
  496. .content-box {
  497. flex: 1;
  498. margin-top: rpx(10);
  499. margin-bottom: rpx(10);
  500. margin-right: rpx(10);
  501. border-radius: rpx(15);
  502. background: rgba($color: #ffffff, $alpha: 0.5);
  503. }
  504. //左侧展览区图标
  505. .img-box {
  506. margin-top: rpx(50);
  507. color: #a39dce;
  508. }
  509. // 对话框
  510. .chat-dialog {
  511. display: flex;
  512. flex-direction: column;
  513. height: 100%;
  514. }
  515. .message-list {
  516. flex: 1;
  517. overflow-y: auto;
  518. padding: rpx(15);
  519. }
  520. .message-list .user-message {
  521. background-color: #ffffff;
  522. margin-left: auto; // 消息靠右显示
  523. margin-right: 0; // 重置右边距
  524. max-width: rpx(400);
  525. font-size: rpx(8);
  526. width: fit-content; // 宽度随文字内容变化
  527. border-radius: rpx(5);
  528. padding: rpx(5);
  529. text-align: left; // 文字左对齐
  530. }
  531. .message-list .ai-message {
  532. background-color: #ffdd55;
  533. margin-left: 0; // 消息靠左显示
  534. margin-right: auto; // 重置右边距
  535. margin-bottom: rpx(10);
  536. width: fit-content;
  537. max-width: rpx(400);
  538. padding: rpx(5);
  539. font-size: rpx(8);
  540. border-radius: rpx(5);
  541. text-align: left; // 文字左对齐
  542. }
  543. .input-section {
  544. display: flex;
  545. padding: rpx(10);
  546. gap: rpx(10);
  547. }
  548. .input-section input {
  549. flex: 1;
  550. padding: rpx(5);
  551. font-size: rpx(7);
  552. border: 1px solid #ccc;
  553. border-radius: rpx(5);
  554. }
  555. .input-section button {
  556. padding: rpx(5) rpx(15);
  557. background: linear-gradient(
  558. to bottom,
  559. #fee78a,
  560. #ffce1b
  561. ); /* 设置悬停、聚焦、点击状态下的背景色 */
  562. color: black;
  563. border: none;
  564. font-size: rpx(7);
  565. border-radius: rpx(5);
  566. cursor: pointer;
  567. box-shadow: 0 4px 8px rgba(202, 52, 52, 0.3);
  568. }
  569. .image-list {
  570. display: flex;
  571. flex-wrap: wrap;
  572. }
  573. .content-demo {
  574. background-color: #f4f2fa;
  575. border-radius: 15px;
  576. padding: 30px 10px;
  577. }
  578. </style>