Эх сурвалжийг харах

图生图添加风格选项

丸子 5 сар өмнө
parent
commit
0bc3885a2e

BIN
src/assets/icon/avatar.png


BIN
src/assets/icon/avatar02.png


+ 2 - 0
src/components/LeftPanel.vue

@@ -49,6 +49,7 @@ import video from '@/assets/icon/video.png'
 import labImage from '@/assets/icon/labImage.png'
 import en from '@/assets/icon/en.png'
 import blockly from '@/assets/icon/blockly.png'
+import avatar from '@/assets/icon/avatar.png'
 // 黑色
 import question02 from '@/assets/icon/question02.png'
 import painting02 from '@/assets/icon/painting02.png'
@@ -58,6 +59,7 @@ import video02 from '@/assets/icon/video02.png'
 import labImage02 from '@/assets/icon/labImage02.png'
 import en02 from '@/assets/icon/en02.png'
 import blockly02 from '@/assets/icon/blockly02.png'
+import avatar02 from '@/assets/icon/avatar02.png'
 
 
  

+ 504 - 0
src/components/ai/image/Avatar.vue

@@ -0,0 +1,504 @@
+<template>
+  <!-- 图生图 -->
+  <div class="number-people">
+    <div class="content-box">
+      <!-- AI对话框 -->
+      <div class="chat-dialog">
+        <!-- 对话消息列表 -->
+        <div class="message-list">
+          <div v-if="imageAllList.length > 0">
+              <div  v-for="(item, index) in imageAllList" :key="index">
+            <!-- 用户消息 -->
+            <div class="user-message" v-if="item.type === 'user'">
+              {{ item.content }}
+              <div class="user-image-list" v-if="item.imageUrl">
+                <el-image
+                  style="width: fit-content; height: 180px; margin: 10px;"
+                  :src="item.imageUrl"
+                  :preview-src-list="[item.imageUrl]"
+                  fit="cover"
+                  show-progress
+                >
+                  <template
+                    #toolbar="{ actions, prev, next, reset, activeIndex, setActiveItem }"
+                  >
+                    <el-icon @click="prev"><Back /></el-icon>
+                    <el-icon @click="next"><Right /></el-icon>
+                    <el-icon @click="setActiveItem(item.imageList.length - 1)">
+                      <DArrowRight />
+                    </el-icon>
+                    <el-icon @click="actions('zoomOut')"><ZoomOut /></el-icon>
+                    <el-icon @click="actions('zoomIn', { enableTransition: false, zoomRate: 2 })"><ZoomIn /></el-icon>
+                    <el-icon @click="actions('clockwise', { rotateDeg: 180, enableTransition: false })"><RefreshRight /></el-icon>
+                    <el-icon @click="actions('anticlockwise')"><RefreshLeft /></el-icon>
+                    <el-icon @click="reset"><Refresh /></el-icon>
+                    <el-icon @click="download(activeIndex)"><Download /></el-icon>
+                  </template>
+                </el-image>
+              </div>
+            </div>
+            <!-- AI生成图片对话框 -->
+            <div class="ai-message" v-if="item.type !== 'user'">
+              {{ item.content }}
+              <span v-if="item.loading" class="loading-dots">
+                <span class="dot"></span>
+                <span class="dot"></span>
+                <span class="dot"></span>
+              </span>
+              <div class="image-list" v-if="item.imageList">
+                <el-image
+                    v-for="(image, index) in item.imageList"
+                    :key="index"
+                    style=" width: fit-content; height: 220px; margin: 10px;"
+                    :src="image"
+                    :preview-src-list="item.imageList"
+                    fit="cover"
+                    show-progress
+                >
+                  <template
+                      #toolbar="{ actions,  reset, activeIndex}"
+                  >
+                    <el-icon @click="actions('zoomOut')"><ZoomOut /></el-icon>
+                    <el-icon
+                        @click="actions('zoomIn', { enableTransition: false, zoomRate: 2 })">
+                      <ZoomIn />
+                    </el-icon>
+                    <el-icon
+                        @click="actions('clockwise', { rotateDeg: 180, enableTransition: false })">
+                      <RefreshRight />
+                    </el-icon>
+                    <el-icon @click="actions('anticlockwise')"><RefreshLeft /></el-icon>
+                    <el-icon @click="reset"><Refresh /></el-icon>
+                    <el-icon @click="download(activeIndex)"><Download /></el-icon>
+                  </template>
+                </el-image>
+              </div>
+            </div>
+             </div>
+          </div>
+        </div>
+
+        <!-- 发送按钮 -->
+        <div class="input-section">
+          <!-- 参考图 -->
+           <ImageUpload v-model="uploadedImage" ref="imageUploadRef"/>
+          <!-- 终止按钮 -->
+          <div
+            v-if="conversationInProgress"
+            @click="stopStream"
+            class="stop-btn"
+            title="终止问答"
+          >
+            <img :src="stopicon" alt="停止" />
+          </div>
+          <button v-if="!conversationInProgress"
+            @click="sendMessage">发送</button>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted,onUnmounted} from 'vue'
+import {AiImageStatusEnum, CreatePainting, PaintingGetMys} from '@/api/questions.js'
+import { useRouter, useRoute } from 'vue-router'
+import {
+  Document,
+  Menu as IconMenu,
+  Location,
+  Setting,
+  ArrowLeftBold,
+  Fold,
+  Expand,
+  ChatLineRound,
+  Picture,
+  MagicStick,
+  Tickets,
+  User
+} from '@element-plus/icons-vue'
+
+import { saveRecord } from '@/api/personalized/index.js'
+
+// 导入全局状态
+import { globalState } from '@/utils/globalState.js'
+// 终止按钮
+import stopicon from "@/assets/icon/stopicon.png";
+// 消息组件
+import {Message} from "@/utils/message/Message.js";
+
+// 上传参考图
+import ImageUpload from '@/components/ImageUpload/index.vue';
+
+// 导入接口
+import { getModelIdByType } from '@/api/teachers.js'
+import { ModelTypeEnum } from '@/api/teachers.js'
+
+// 存储上传的图片
+const uploadedImage = ref('');
+
+const imageUploadRef = ref(null);
+
+
+// 对话状态变量
+const conversationInProgress = ref(false); // 对话是否正在进行中
+const conversationInAbortController = ref(); // 对话进行中 abort 控制器
+
+// tts 语音
+import { useAudioPlayer } from '@/api/tts/useAudioPlayer';
+
+// 添加抽屉显示状态
+const drawerVisible = ref(true)
+
+  // 年级ID相关
+const gradeId = ref('')
+// 添加消息计数器变量
+const messageCount = ref(0)
+// modelId响应式变量
+const modelId = ref(0)
+// 保存记录
+onMounted(async () => {
+    // 从全局状态初始化年级ID
+  gradeId.value = globalState.initGradeId()
+  try{
+    const res = await saveRecord({
+        brpNjId: gradeId.value,
+        brpType: "aiCount",
+        brpProgress: 1
+      });
+      // 获取modelId
+    const modelRes = await getModelIdByType({ type: ModelTypeEnum.IMAGE_TO_IMAGE, platform: "DouBao" })
+    modelId.value = modelRes.data
+  }catch(error){
+    console.error('保存记录失败:', error);
+  }
+});
+
+// 停止操作函数
+const stopStream = async () => {
+  // tip:如果 stream 进行中的 message,就需要调用 controller 结束
+  if (conversationInAbortController.value) {
+    conversationInAbortController.value.abort();
+  }
+  // 设置为 false
+  conversationInProgress.value = false;
+};
+
+// 发送消息函数,图片数据
+const sendMessage = async() => {
+  if (uploadedImage.value) {
+    // 创建 AbortController 实例,以便中止请求
+    conversationInAbortController.value = new AbortController();
+    // 标记对话进行中
+    conversationInProgress.value = true;
+    
+    // 创建用户消息对象,包含图片
+    const userMessage = {
+      type: 'user',
+      content: '', // 删除了inputMessage内容
+    };
+    
+    // 添加上传的图片到用户消息中
+    userMessage.imageUrl = uploadedImage.value;
+    
+    imageAllList.value.push(userMessage);
+    imageAllList.value.push({
+      type: 'ai',
+      content: "正在为您生成图片,请稍等",
+      loading: true
+    })
+
+    // 递增消息计数器
+    messageCount.value++
+    // 发送saveRecord请求 保存消息次数
+    try{
+      await saveRecord({
+         brpNjId: gradeId.value,
+         brpType: "aiCount",
+         brpProgress: messageCount.value
+       });
+       console.log('保存记录成功,消息次数:', messageCount.value);
+   }catch(error){
+     console.error('保存记录失败:', error);
+     conversationInProgress.value = false;
+   }
+
+    try {
+      CreatePainting({
+        "modelId": modelId.value,
+        "prompt":'', 
+        "width":1024,
+        "height":1024,
+        "promptImage":uploadedImage.value
+      }).then(res=>{
+        console.log("生成图片",res)
+        //目前写死调用已生成的图片,全部通了后再改
+        inProgressImageMap.value[res.data] = {id:res.data,status:AiImageStatusEnum.IN_PROGRESS}
+        // inProgressImageMap.value[260] = {id:260,status:AiImageStatusEnum.IN_PROGRESS}
+      }).finally(() => {
+        // 图片生成请求完成后更新状态
+        conversationInProgress.value = false;
+      });
+    } catch (error) {
+      console.error('生成图片失败:', error);
+      conversationInProgress.value = false;
+    }
+  } else {
+    // 如果没有上传图片,显示提示信息
+    Message().error('请先上传参考图!', true);
+  }
+  // 调用子组件的方法清除预览图
+  imageUploadRef.value?.clearPreview();
+};
+
+
+// 生成图片
+import { ElIcon } from 'element-plus'
+import {
+  Back,
+  DArrowRight,
+  Download,
+  Refresh,
+  RefreshLeft,
+  RefreshRight,
+  Right,
+  ZoomIn,
+  ZoomOut,
+} from '@element-plus/icons-vue'
+
+
+const imageAllList = ref([]) // 对话的消息列表
+// 图片轮询相关的参数(正在生成中的)
+const inProgressImageMap = ref({}) // 监听的 image 映射,一般是生成中(需要轮询),key 为 image 编号,value 为 image
+const inProgressTimer = ref() // 生成中的 image 定时器,轮询生成进展
+
+
+/** 轮询生成中的 image 列表 */
+const refreshWatchImages = async () => {
+  const imageIds = Object.keys(inProgressImageMap.value).map(Number)
+  if (imageIds.length === 0) {
+    return
+  }
+  const list = await PaintingGetMys(imageIds)
+  const newWatchImages = {}
+  list.data.forEach((image) => {
+    
+    if (image.status === AiImageStatusEnum.IN_PROGRESS) {
+      newWatchImages[image.id] = image
+    } else {
+      imageAllList.value.pop();
+      console.log('AI生成的图片地址:', image.picUrl);
+      imageAllList.value.push({
+        type: 'ai',
+        content: "已为您生成图片:",
+        imageList: [image.picUrl],
+      })
+    }
+  })
+  inProgressImageMap.value = newWatchImages
+  if (newWatchImages.size === 0) {
+    inProgressTimerFun()
+  }
+}
+
+
+/** 组件挂在的时候 */
+onMounted(async () => {
+  refreshWatchImagesFun()
+})
+
+/** 组件取消挂在的时候 */
+onUnmounted(async () => {
+  inProgressTimerFun()
+})
+
+// 自动刷新 image 列表
+const refreshWatchImagesFun = () => {
+  inProgressTimer.value = setInterval(async () => {
+    await refreshWatchImages()
+  }, 1000 * 3)
+}
+
+// 停止刷新image列表
+const inProgressTimerFun = () => {
+  if (inProgressTimer.value) {
+    clearInterval(inProgressTimer.value)
+  }
+}
+</script>
+
+<style scoped lang="scss">
+@use 'sass:math';
+// 定义rpx转换函数
+@function rpx($px) {
+  @return math.div($px, 750) * 100vw;
+}
+// 用户图片列表样式
+.user-image-list {
+  display: flex;
+  flex-wrap: wrap;
+  margin-top: rpx(5);
+}
+.number-people {
+  flex: 1;
+  height: 100%;
+  display: flex;
+  background-color: #ece9fd;
+}
+
+.content-box {
+  flex: 1;
+  // margin-top: rpx(10);
+  // margin-bottom: rpx(10);
+  margin: rpx(7);
+  border-radius: rpx(15);
+  background: rgba($color: #ffffff, $alpha: 0.5);
+  overflow-y: auto;
+}
+
+// 对话框
+.chat-dialog {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+}
+.message-list {
+  flex: 1;
+  overflow-y: auto;
+  padding: rpx(15);
+}
+/* 自定义滚动条样式 */
+.message-list::-webkit-scrollbar {
+  width: rpx(2); /* 滚动条宽度 */
+}
+.message-list::-webkit-scrollbar-track {
+  background: #f1effd; /* 滚动条轨道背景色 */
+  border-radius: rpx(4);
+}
+.message-list::-webkit-scrollbar-thumb {
+  background: #e2ddfc; /* 滚动条滑块颜色 */
+  border-radius: rpx(4);
+}
+.message-list::-webkit-scrollbar-thumb:hover {
+  background: #e2ddfc; /* 滚动条滑块 hover 状态颜色 */
+}
+
+.message-list .user-message {
+  background-color: #ffffff;
+  margin-left: auto; // 消息靠右显示
+  margin-right: 0; // 重置右边距
+  max-width: rpx(400);
+  font-size: rpx(8);
+  width: fit-content; // 宽度随文字内容变化
+  border-radius: rpx(5);
+  padding: rpx(5);
+  text-align: left; // 文字左对齐
+}
+
+.message-list .ai-message {
+  background-color: #ffdd55;
+  margin-left: 0; // 消息靠左显示
+  margin-right: auto; // 重置右边距
+  margin-bottom: rpx(10);
+  width: fit-content;
+  max-width: rpx(400);
+  padding: rpx(5);
+  font-size: rpx(8);
+  border-radius: rpx(5);
+  text-align: left; // 文字左对齐
+}
+
+// 加载动画效果
+.loading-dots {
+  display: inline-block;
+  margin-left: rpx(5);
+}
+.loading-dots .dot {
+  display: inline-block;
+  width: rpx(3);
+  height: rpx(3);
+  border-radius: 50%;
+  background-color: #333;
+  margin: 0 rpx(1);
+  animation: loading-dot 1.4s infinite ease-in-out both;
+}
+.loading-dots .dot:nth-child(1) {
+  animation-delay: -0.32s;
+}
+.loading-dots .dot:nth-child(2) {
+  animation-delay: -0.16s;
+}
+@keyframes loading-dot {
+  0%, 80%, 100% {
+    transform: scale(0);
+  }
+  40% {
+    transform: scale(1);
+  }
+}
+
+.image-list {
+  display: flex;
+  flex-wrap: wrap;
+}
+
+
+.content-demo {
+  background-color: #f4f2fa;
+  border-radius: 15px;
+  padding: 30px 10px;
+}
+
+.input-section {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: rpx(10);
+  gap: rpx(5);
+  
+  .left-section {
+    flex: 1;
+    display: flex;
+    justify-content: flex-start;
+  }
+  
+  .right-section {
+    display: flex;
+    align-items: center;
+    gap: rpx(10);
+  }
+  
+  // 终止按钮样式
+  .stop-btn {
+    cursor: pointer;
+    display: flex;
+    align-items: center;
+    img {
+      width: rpx(20);
+      height: rpx(20);
+    }
+  }
+}
+.input-section button {
+  padding: rpx(5) rpx(15);
+  background: linear-gradient(
+    to bottom,
+    #fee78a,
+    #ffce1b
+  ); /* 设置悬停、聚焦、点击状态下的背景色 */
+  color: black;
+  border: none;
+  font-size: rpx(7);
+  border-radius: rpx(5);
+  cursor: pointer;
+    box-shadow: 0 0px 2px rgba(0, 0, 0, 0.3);
+
+}
+
+.image-upload-section {
+  padding: rpx(10);
+  display: flex;
+  justify-content: center;
+  align-items: center;
+}
+</style>

+ 105 - 5
src/components/ai/image/ImageToImage.vue

@@ -81,12 +81,28 @@
         <!-- 输入框和发送按钮 -->
         <div class="input-section">
           <input
+            ref="inputRef"
             type="text"
             v-model="inputMessage"
             placeholder="描述任何画面..."
             @keyup.enter="sendMessage"
             style="flex: 1; margin-right: 8px;"
           />
+          <!-- 风格选择下拉框 -->
+          <el-select 
+            v-model="selectedStyle" 
+            placeholder="选择风格"
+            @change="handleStyleSelectChange"
+            size="small"
+            class="custom-style-select"
+          >
+            <el-option 
+              v-for="style in styleList" 
+              :key="style" 
+              :label="style" 
+              :value="style"
+            />
+          </el-select>
           <!-- 参考图 -->
            <ImageUpload v-model="uploadedImage" ref="imageUploadRef"/>
           <!-- 语音输入按钮 -->
@@ -134,7 +150,12 @@ import {
   Picture,
   MagicStick,
   Tickets,
-  User
+  User,
+  Brush,
+  Check,
+  EditPen,
+  ArrowDown,
+  ArrowUp
 } from '@element-plus/icons-vue'
 
 import { saveRecord } from '@/api/personalized/index.js'
@@ -148,10 +169,10 @@ import stopicon from "@/assets/icon/stopicon.png";
 // 消息组件
 import {Message} from "@/utils/message/Message.js";
 
-// 图生
+// 上传参考
 import ImageUpload from '@/components/ImageUpload/index.vue';
 
-// 导入getModelIdByType接口
+// 导入接口
 import { getModelIdByType } from '@/api/teachers.js'
 import { ModelTypeEnum } from '@/api/teachers.js'
 
@@ -160,11 +181,18 @@ const uploadedImage = ref('');
 
 const imageUploadRef = ref(null);
 
+// 当前选中的风格
+const selectedStyle = ref('');
+// 已移除dropdownVisible,使用el-select的v-model
+
 // 语音输入响应式变量
 const isRecording = ref(false); // 录音状态
 const recognition = ref(null); // 语音识别实例
 const countdown = ref(0); // 倒计时剩余秒数
-const countdownTimer = ref(null); // 倒计时定
+const countdownTimer = ref(null); // 倒计时定时器
+
+// 风格列表
+const styleList = ['电影写真', '中国风', '卡通', '动漫', '素描', '像素风格'];
 // 对话状态变量
 const conversationInProgress = ref(false); // 对话是否正在进行中
 const conversationInAbortController = ref(); // 对话进行中 abort 控制器
@@ -201,6 +229,8 @@ onMounted(async () => {
 
 // 消息列表和输入内容的响应式变量
 const inputMessage = ref('')
+// 输入框引用
+const inputRef = ref(null)
 
 // 初始化语音识别
 const initSpeechRecognition = () => {
@@ -442,6 +472,22 @@ const inProgressTimerFun = () => {
     clearInterval(inProgressTimer.value)
   }
 }
+
+// 处理风格选择变化事件
+const handleStyleSelectChange = (style) => {
+  // 检查输入框中是否已经包含风格描述,如果有则替换,没有则添加
+  const styleRegex = /生成图片风格为[^,]+/g;
+  if (styleRegex.test(inputMessage.value)) {
+    // 替换现有的风格描述
+    inputMessage.value = inputMessage.value.replace(styleRegex, `生成图片风格为${style}`);
+  } else {
+    // 添加新的风格描述
+    inputMessage.value += `生成图片风格为${style}`;
+  }
+  // 选中风格后,将光标自动对焦到输入
+  inputRef.value?.focus();
+}
+
 </script>
 
 <style scoped lang="scss">
@@ -456,7 +502,6 @@ const inProgressTimerFun = () => {
   flex-wrap: wrap;
   margin-top: rpx(5);
 }
-//===========================================全局除了删除侧边栏内容唯一改动的地方
 .number-people {
   flex: 1;
   height: 100%;
@@ -567,6 +612,41 @@ const inProgressTimerFun = () => {
   padding: 30px 10px;
 }
 
+// 风格下拉框
+.style-dropdown{
+  // min-width: auto;
+  width: fit-content;
+  padding: rpx(6);
+}
+.style-dropdown ::v-deep(.el-dropdown-menu__item) {
+  font-size: rpx(8);
+  color: black;
+  border-radius: rpx(5);
+  min-width: auto; 
+  width: rpx(55);
+  height: rpx(15);
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding-right: rpx(10);
+}
+.selected-icon {
+    color: black;
+    font-size: rpx(10);
+  }
+  .style-dropdown ::v-deep(.el-dropdown-menu__item:hover),
+  .style-dropdown ::v-deep(.el-dropdown-menu__item:focus),
+  .style-dropdown ::v-deep(.el-dropdown-menu__item:active) {
+    background: linear-gradient(
+      to bottom,
+      #fee78a,
+      #ffce1b
+    );
+  }
+  
+  
+
+
 .input-section {
   display: flex;
   padding: rpx(10);
@@ -579,6 +659,7 @@ const inProgressTimerFun = () => {
     cursor: pointer;
     display: flex;
     align-items: center;
+    gap: rpx(4);
     &.recording {
       background: #ffeeba;
       border-color: #ffc107;
@@ -591,6 +672,10 @@ const inProgressTimerFun = () => {
       font-size: rpx(8);
       color: #666;
     }
+    .dropdown-icon {
+      font-size: rpx(6);
+      transition: transform 0.3s ease;
+    }
   }
   // 终止按钮样式
   .stop-btn {
@@ -632,4 +717,19 @@ const inProgressTimerFun = () => {
   justify-content: center;
   align-items: center;
 }
+
+// 风格下拉框
+.custom-style-select {
+    width: rpx(55);
+  }
+.custom-style-select ::v-deep(.el-select__wrapper) {
+    background-color: #ffff;
+     width: rpx(55);
+     height: rpx(20);
+     font-size: rpx(6);
+     color: black;
+     border: 1px solid #ffce1b;
+    border-radius: rpx(5);
+  }
 </style>
+

+ 12 - 7
src/router/index.js

@@ -16,12 +16,12 @@ const routes = [
   // 智能课
   {
     path: '/ai-general-course',
-    component: () => import('../views/AIGeneralCourse.vue')
+    component: () => import('../views/AIPage/AIGeneralCourse.vue')
   },
   // AI实验室
   {
     path: '/ai-laboratory',
-    component: () => import('../views/AILaboratory.vue')
+    component: () => import('../views/AIPage/AILaboratory.vue')
   },
   // 英文数字人老师
   {
@@ -51,27 +51,32 @@ const routes = [
   // 智能绘画
   {
     path: '/ai-painting',
-    component: () => import('../views/AIPainting.vue')
+    component: () => import('../views/AIPage/AIPainting.vue')
   },
   // 图生图
   {
     path: '/ai-image',
-    component: () => import('../views/AIImageToImage.vue')
+    component: () => import('../views/AIPage/AIImageToImage.vue')
+  },
+  // AI头像
+  {
+    path: '/ai-avatar',
+    component: () => import('../views/AIPage/AIAvatar.vue')
   },
   // 图生视频
   {
     path: '/ai-video',
-    component: () => import('../views/AIImageToVideo.vue')
+    component: () => import('../views/AIPage/AIImageToVideo.vue')
   },
   // 智能问答
   {
     path: '/ai-questions',
-    component: () => import('../views/AIQuestions.vue')
+    component: () => import('../views/AIPage/AIQuestions.vue')
   },
   // 发展历程
   {
     path: '/ai-develop',
-    component: () => import('../views/AIDevelop.vue')
+    component: () => import('../views/AIPage/AIDevelop.vue')
   },
   // 虚拟实验室
   {

+ 160 - 0
src/views/AIPage/AIAvatar.vue

@@ -0,0 +1,160 @@
+<template>
+  <!-- AI头像 -->
+  <div class="home-container">
+    <!-- 展开收起侧边栏 -->
+    <div
+      class="icon-expand"
+      :style="{
+        backgroundColor: drawerVisible ? '#44449c' : '#7F70C840',
+        left: drawerVisible ? '18%' : '0'
+      }"
+      @click="toggleDrawer"
+    >
+      <span
+        class="vertical-lines"
+        :style="{
+          color: drawerVisible ? '#8a78d0' : 'white'
+        }"
+        >||</span
+      >
+    </div>
+
+    <!-- 左侧折叠面板 -->
+    <LeftPanel ref="leftPanelRef" v-if="drawerVisible"/>
+
+    <div class="left-group2">
+      <div class="title-box">
+        <div class="box-icon" @click="goBack">
+          <el-icon class="left-icon"><ArrowLeftBold /></el-icon>
+          AI头像
+        </div>
+      </div>
+      <div class="img-box">
+        <p>
+          <img
+              style="width: fit-content; height: 180px; margin: 10px;"
+              src="@/assets/images/color.png"
+              class="avatar user"
+          />
+        </p>
+        <p>期待你的画作喔~</p>
+      </div>
+    </div>
+
+    <!-- 图生图 -->
+    <Avatar />
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted } from 'vue'
+import { useRouter } from 'vue-router'
+import { ArrowLeftBold } from '@element-plus/icons-vue'
+import LeftPanel from '@/components/LeftPanel.vue'
+import Avatar from '@/components/ai/image/Avatar.vue'
+
+const router = useRouter()
+const leftPanelRef = ref(null)
+const drawerVisible = ref(true)
+
+// 切换抽屉显示状态
+const toggleDrawer = () => {
+  drawerVisible.value = !drawerVisible.value
+}
+
+// 返回上一页
+const goBack = () => {
+  router.push('/ai-laboratory')
+}
+</script>
+
+<style scoped lang="scss">
+@use 'sass:math';
+// 定义rpx转换函数
+@function rpx($px) {
+  @return math.div($px, 750) * 100vw;
+}
+
+:deep(.el-image-viewer__wrapper) {
+  z-index: 10000 !important;
+}
+
+.icon-expand {
+  width: rpx(8);
+  height: rpx(35);
+  border-top-right-radius: rpx(5);
+  border-bottom-right-radius: rpx(5);
+  z-index: 9999;
+  position: absolute;
+  top: 50%;
+  left: 18%;
+  transform: translateY(-50%);
+  background-color: #44449c;
+  cursor: pointer; // 添加鼠标指针样式
+  clip-path: polygon(0 0, 100% 15%, 100% 85%, 0 100%);
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  transition: all 0.3s ease;
+}
+
+.icon-expand .vertical-lines {
+  color: #8a78d0;
+  font-size: rpx(10);
+}
+
+.home-container {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  display: flex;
+  flex-direction: row;
+  gap: rpx(0);
+  background-color: #ece9fd;
+  // background: linear-gradient(
+  //   to bottom,
+  //   #e2ddfc,
+  //   #f1effd
+  // );
+}
+
+// 侧边栏
+.left-group2 {
+  width: rpx(150);
+  height: 100%;
+}
+
+.left-group2 img {
+  width: rpx(110);
+  height: auto;
+  margin-top: rpx(30);
+}
+
+.title-box {
+  height: rpx(50);
+}
+
+.box-icon {
+  width: 100%;
+  height: 100%;
+  flex: 1;
+  display: flex; // 添加 flex 布局
+  align-items: center; // 垂直居中
+  color: black; // 设置图标颜色为白色
+  padding-left: rpx(15);
+  font-size: rpx(10); // 设置图标大小,可按需调整
+  cursor: pointer; // 添加鼠标指针样式
+}
+
+.box-icon .left-icon {
+  margin-left: rpx(10);
+  margin-right: rpx(5); // 设置图标和文字之间的间距 ;
+}
+
+.img-box {
+  margin-top: rpx(50);
+  color: #a39dce;
+}
+</style>

+ 0 - 0
src/views/AIDevelop.vue → src/views/AIPage/AIDevelop.vue


+ 0 - 0
src/views/AIGeneralCourse.vue → src/views/AIPage/AIGeneralCourse.vue


+ 0 - 0
src/views/AIImageToImage.vue → src/views/AIPage/AIImageToImage.vue


+ 0 - 0
src/views/AIImageToVideo.vue → src/views/AIPage/AIImageToVideo.vue


+ 0 - 0
src/views/AILaboratory.vue → src/views/AIPage/AILaboratory.vue


+ 0 - 0
src/views/AIPainting.vue → src/views/AIPage/AIPainting.vue


+ 0 - 0
src/views/AIQuestions.vue → src/views/AIPage/AIQuestions.vue