AIPainting.vue 16 KB

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