丸子 10 bulan lalu
induk
melakukan
9a66c9994a

+ 59 - 16
src/views/AIDevelop.vue

@@ -76,6 +76,7 @@
       <video
         class="full-box-video"
         :src="videoSrc"
+        controlsList="nodownload"
         controls
         @timeupdate="handleTimeUpdate"
         ref="videoRef"
@@ -87,11 +88,11 @@
       <div class="video-switch">
         <!-- 上一个视频 -->
         <div class="caret-left" @click="playPreviousVideo">
-          <el-icon class="caret-left-icon"><CaretLeft /></el-icon>
+          <el-button type="warning" round>上一个</el-button>
         </div>
         <!-- 下一个视频 -->
         <div class="caret-right" @click="playNextVideo">
-          <el-icon class="caret-right-icon"><CaretRight /></el-icon>
+          <el-button type="warning" round>下一个</el-button>
         </div>
       </div>
     </div>
@@ -327,21 +328,23 @@ const flattenMenuItems = () => {
   return indices
 }
 
-// 自动播放下一个视频
-const playNextVideo = () => {
+// 播放下一个视频
+const playNextVideo = () => { 
   const allIndices = flattenMenuItems()
   const currentIndexInList = allIndices.indexOf(currentIndex.value)
   if (currentIndexInList !== -1 && currentIndexInList < allIndices.length - 1) {
     const nextIndex = allIndices[currentIndexInList + 1]
     handleSelect(nextIndex)
+    // 切换视频后自动播放
+    if (videoRef.value) {
+      videoRef.value.addEventListener('loadedmetadata', tryPlayVideo, { once: true })
+    }
   }
-
-  //重置
+  // 重置
   pausedIndices = ref([])
   userMessage = ref('')
   messageHistory = ref([])
 }
-
 // 切换视频
 // 播放上一个视频
 const playPreviousVideo = () => {
@@ -350,9 +353,23 @@ const playPreviousVideo = () => {
   if (currentIndexInList > 0) {
     const previousIndex = allIndices[currentIndexInList - 1]
     handleSelect(previousIndex)
+    // 切换视频后自动播放
+    if (videoRef.value) {
+      videoRef.value.addEventListener('loadedmetadata', tryPlayVideo, { once: true })
+    }
+  }
+}
+// 尝试播放视频,处理浏览器自动播放限制
+const tryPlayVideo = () => {
+  const playPromise = videoRef.value.play()
+  if (playPromise !== undefined) {
+    playPromise.catch(error => {
+      console.error('视频播放失败,可能是浏览器自动播放限制:', error)
+    })
   }
 }
 
+
 // 视频 ref
 const videoRef = ref(null)
 // 记录已经暂停过的时间点索引
@@ -584,6 +601,8 @@ const handleCloseAIDialog = () => {
   showAIDialog.value = false
 }
 </script>
+
+
 <style scoped lang="scss">
 @use 'sass:math';
 // 定义rpx转换函数
@@ -812,6 +831,21 @@ $text-color: #483d8b; // 文本颜色:靛蓝色
     // object-fit: contain;
   }
 }
+// 视频进度条样式
+video.full-box-video::-webkit-media-controls-timeline {
+  background-color: rgba(255, 255, 255,0.3); // 进度条背景颜色,调浅为半透明白色
+}
+video.full-box-video::-webkit-media-controls-current-time-display,
+video.full-box-video::-webkit-media-controls-time-remaining-display {
+  color: white; // 时间显示颜色
+}
+video.full-box-video::-webkit-media-controls-progress-bar {
+  background-color: rgba(255, 255, 255, 0.5); // 已播放进度颜色,调浅为半透明白色
+}
+video.full-box-video::-webkit-media-controls-thumb {
+  background-color: white; // 滑块颜色
+}
+
 .small-title {
   width: 100%;
   margin-top: rpx(-20);
@@ -824,21 +858,30 @@ $text-color: #483d8b; // 文本颜色:靛蓝色
   width: 100%;
   // height: rpx(50);
   display: flex;
-  margin-top: rpx(10);
+  // margin-top: rpx(10);
   // background-color: saddlebrown;
 }
 .caret-right,
 .caret-left{
-  width: rpx(20);
-  height: rpx(20);
+  width: rpx(50);
+  // height: rpx(50);
   margin: auto;
-  border-radius: rpx(50);
-  border: 2px solid white;
+  display: flex;
+  // grid: rpx(4);
+  margin-top: rpx(5);
+  margin-bottom: rpx(5);
 }
-.caret-right-icon,
-.caret-left-icon{
-  font-size: rpx(20);
-  color: white;
+.caret-left ::v-deep(.el-button.is-round) ,
+.caret-right ::v-deep(.el-button.is-round) {
+  width: rpx(50);
+  color: black;
+  border-radius: none;
+  background: linear-gradient(
+    to bottom,
+    #fee78a,
+    #ffce1b
+  ); /* 设置悬停、聚焦、点击状态下的背景色 */
+    box-shadow: 0 4px 8px rgba(202, 52, 52, 0.1);
 }
 
 // 儿童风格试题弹框样式

+ 1 - 1
src/views/AIInitialExperience.vue

@@ -18,7 +18,7 @@
           <el-col :span="12">
             <h3 class="mb-2">课程小节</h3>
             <el-menu
-            default-active="1"
+              default-active="1"
               class="el-menu-vertical-demo"
               @open="handleOpen"
               @close="handleClose"

+ 2 - 16
src/views/AIPainting.vue

@@ -26,7 +26,6 @@
 
               <!-- AI生成图片对话框 -->
               <div class="ai-message" v-if="item.type !== 'user'">
-
                 {{ item.content }}
 
                 <div class="image-list" v-if="item.imageList">
@@ -72,6 +71,7 @@
               type="text"
               v-model="inputMessage"
               placeholder="说说你的灵感..."
+              @keyup.enter="sendMessage"
             />
             <button @click="sendMessage">发送</button>
           </div>
@@ -100,18 +100,6 @@ const goBack = () => {
 const router = useRouter()
 const route = useRoute()
 
-// AI生成图片
-// CreatePainting({
-//   modelId: 56,
-//   prompt:''
-// }).then(res=>{
-//   console.log(res);
-// })
-
-// 获取绘图记录
-// CreatePaintingGetMy().then(res=>{
-//   console.log(res);
-// })
 
 // 消息列表和输入内容的响应式变量
 const messages = ref([])
@@ -122,10 +110,9 @@ const sendMessage = () => {
   console.log(inputMessage.value)
   if (inputMessage.value.trim()) {
     // messages.value.push(inputMessage.value.trim())
-
+    // 先保存内容 再置空输入框
     let content = inputMessage.value;
     inputMessage.value = ''
-
     imageAllList.value.push({
       type: 'user',
       content: content,
@@ -134,7 +121,6 @@ const sendMessage = () => {
       type: 'ai',
       content: "正在为您生成图片,请稍等...",
     })
-
     CreatePainting({
       "modelId": 56,
       "prompt":content,

+ 71 - 77
src/views/AIQuestions.vue

@@ -9,7 +9,9 @@
           {{ personName }}
         </div>
       </div>
-      <img :src="selectedImage" alt="" />
+      <div class="selected-image">
+        <img :src="selectedImage" alt="" />
+      </div>
     </div>
     <!-- 右侧AI问答 -->
     <div class="number-people">
@@ -19,7 +21,6 @@
           <!-- 对话消息列表 -->
           <div class="message-list">
             <div v-for="(item, index) in messageList" :key="index">
-
               <!-- AI消息 -->
               <div class="ai-message" v-if="item.type !== 'user'">
                 {{ item.content }}
@@ -29,7 +30,6 @@
               <div class="user-message" v-if="item.type === 'user'">
                 {{ item.content }}
               </div>
-
             </div>
           </div>
           <!-- 输入框和发送按钮 -->
@@ -38,6 +38,7 @@
               type="text"
               v-model="prompt"
               placeholder="问我任何问题..."
+              @keyup.enter="sendMessage"
             />
             <button @click="handleSendByButton">发送</button>
           </div>
@@ -48,8 +49,8 @@
 </template>
 
 <script setup>
-import { ref, onMounted, computed} from 'vue'
-import {CreateDialogue, sendChatMessageStream} from '@/api/questions.js'
+import { ref, onMounted, computed } from 'vue'
+import { CreateDialogue, sendChatMessageStream } from '@/api/questions.js'
 import { useRouter, useRoute } from 'vue-router'
 import {
   Document,
@@ -79,21 +80,8 @@ onMounted(() => {
   }
 })
 
-// // 消息列表和输入内容的响应式变量
-// const messages = ref([])
-// const inputMessage = ref('')
-// // 发送消息函数
-// const sendMessage = () => {
-//   if (inputMessage.value.trim()) {
-//     messages.value.push(inputMessage.value.trim())
-//     inputMessage.value = ''
-//   }
-// }
-
-
-
 // 聊天对话
-const activeConversationModelPath= ref(null) // 选中的对话编号
+const activeConversationModelPath = ref(null) // 选中的对话编号
 const activeConversationId = ref(null) // 选中的对话编号
 const activeConversation = ref(null) // 选中的 Conversation
 const conversationInProgress = ref(false) // 对话是否正在进行中。目前只有【发送】消息时,会更新为 true,避免切换对话、删除对话等操作,导致 stream 中断
@@ -117,11 +105,10 @@ const enableContext = ref(true) // 是否开启上下文
 const receiveMessageFullText = ref('')
 const receiveMessageDisplayedText = ref('')
 
-
 // =========== 【聊天对话】相关 ===========
 
 /** 获取对话信息 */
-const getConversation = async (id) => {
+const getConversation = async id => {
   if (!id) {
     return
   }
@@ -135,11 +122,10 @@ const getConversation = async (id) => {
   activeConversationModelPath.value = personImage.value
 }
 
-
 // =========== 【发送消息】相关 ===========
 
 /** 处理来自 keydown 的发送消息 */
-const handleSendByKeydown = async (event) => {
+const handleSendByKeydown = async event => {
   // 判断用户是否在输入
   if (isComposing.value) {
     return
@@ -168,7 +154,7 @@ const handleSendByButton = () => {
 }
 
 /** 处理 prompt 输入变化 */
-const handlePromptInput = (event) => {
+const handlePromptInput = event => {
   // 非输入法 输入设置为 true
   if (!isComposing.value) {
     // 回车 event data 是 null
@@ -197,7 +183,7 @@ const onCompositionend = () => {
 }
 
 /** 真正执行【发送】消息操作 */
-const doSendMessage = async (content) => {
+const doSendMessage = async content => {
   // 校验
   if (content.length < 1) {
     console.error('发送失败,原因:内容为空!')
@@ -218,8 +204,7 @@ const doSendMessage = async (content) => {
 }
 
 /** 真正执行【发送】消息操作 */
-const doSendMessageStream = async (userMessage) => {
-
+const doSendMessageStream = async userMessage => {
   // 创建 AbortController 实例,以便中止请求
   conversationInAbortController.value = new AbortController()
   // 标记对话进行中
@@ -251,41 +236,42 @@ const doSendMessageStream = async (userMessage) => {
     let isFirstChunk = true // 是否是第一个 chunk 消息段
 
     await sendChatMessageStream(
-        userMessage.conversationId,
-        userMessage.content,
-        conversationInAbortController.value,
-        enableContext.value,
-        async (res) => {
-          const { code, data, msg } = JSON.parse(res.data)
-          if (code !== 0) {
-            console.log(`对话异常! ${msg}`)
-            return
-          }
-          // 如果内容为空,就不处理。
-          // if (data.receive.content === '') {
-          //   return
-          // }
-          receiveMessageFullText.value = receiveMessageFullText.value + data.receive.content
-          // 首次返回需要添加一个 message 到页面,后面的都是更新
-          if (isFirstChunk) {
-            isFirstChunk = false
-            // 弹出两个假数据
-            activeMessageList.value.pop()
-            activeMessageList.value.pop()
-            // 更新返回的数据
-            activeMessageList.value.push(data.send)
-            activeMessageList.value.push(data.receive)
-          }
-        },
-        (error) => {
-          console.log(`对话异常! ${error}`)
-          stopStream()
-          // 需要抛出异常,禁止重试
-          throw error
-        },
-        () => {
-          stopStream()
+      userMessage.conversationId,
+      userMessage.content,
+      conversationInAbortController.value,
+      enableContext.value,
+      async res => {
+        const { code, data, msg } = JSON.parse(res.data)
+        if (code !== 0) {
+          console.log(`对话异常! ${msg}`)
+          return
+        }
+        // 如果内容为空,就不处理。
+        // if (data.receive.content === '') {
+        //   return
+        // }
+        receiveMessageFullText.value =
+          receiveMessageFullText.value + data.receive.content
+        // 首次返回需要添加一个 message 到页面,后面的都是更新
+        if (isFirstChunk) {
+          isFirstChunk = false
+          // 弹出两个假数据
+          activeMessageList.value.pop()
+          activeMessageList.value.pop()
+          // 更新返回的数据
+          activeMessageList.value.push(data.send)
+          activeMessageList.value.push(data.receive)
         }
+      },
+      error => {
+        console.log(`对话异常! ${error}`)
+        stopStream()
+        // 需要抛出异常,禁止重试
+        throw error
+      },
+      () => {
+        stopStream()
+      }
     )
   } catch {}
 }
@@ -300,7 +286,6 @@ const stopStream = async () => {
   conversationInProgress.value = false
 }
 
-
 /**
  * 消息列表
  *
@@ -326,9 +311,9 @@ const messageList = computed(() => {
 // ============== 【消息滚动】相关 =============
 
 /** 滚动到 message 底部 */
-const scrollToBottom = async (isIgnore) => {
+const scrollToBottom = async isIgnore => {
   // if (messageRef.value) {
-    // messageRef.value.scrollToBottom(isIgnore)
+  // messageRef.value.scrollToBottom(isIgnore)
   // }
 }
 
@@ -346,7 +331,9 @@ const textRoll = async () => {
     const task = async () => {
       // 调整速度
       const diff =
-          (receiveMessageFullText.value.length - receiveMessageDisplayedText.value.length) / 10
+        (receiveMessageFullText.value.length -
+          receiveMessageDisplayedText.value.length) /
+        10
       if (diff > 5) {
         textSpeed.value = 10
       } else if (diff > 2) {
@@ -366,7 +353,8 @@ const textRoll = async () => {
         index++
 
         // 更新 message
-        const lastMessage = activeMessageList.value[activeMessageList.value.length - 1]
+        const lastMessage =
+          activeMessageList.value[activeMessageList.value.length - 1]
         lastMessage.content = receiveMessageDisplayedText.value
         // 滚动到住下面
         await scrollToBottom()
@@ -387,17 +375,18 @@ const textRoll = async () => {
   } catch {}
 }
 
-
 /** 初始化 **/
 onMounted(async () => {
-  if(personId.value) {
+  if (personId.value) {
     // 智能问答
-    CreateDialogue({roleId: personId.value}).then(res => {
-      console.log("创建会话:",res);
-      activeConversationId.value = res.data;
-    }).catch(error => {
-      console.error('请求出错:', error);
-    });
+    CreateDialogue({ roleId: personId.value })
+      .then(res => {
+        console.log('创建会话:', res)
+        activeConversationId.value = res.data
+      })
+      .catch(error => {
+        console.error('请求出错:', error)
+      })
     await getConversation(personId.value)
   }
   // 获取列表数据
@@ -426,12 +415,17 @@ onMounted(async () => {
 .left-group {
   width: rpx(150);
   height: 100%;
+  display: flex;
   background-color: #ece9fd;
 }
 .left-group img {
   width: rpx(120);
-  height: auto;
-  margin-top: rpx(30);
+  // height: auto;
+}
+.selected-image{
+  flex: 1;
+  margin: auto;
+  margin-left: rpx(-60);
 }
 .title-box {
   height: rpx(50);
@@ -491,7 +485,7 @@ onMounted(async () => {
   text-align: left; // 文字左对齐
 }
 .message-list .ai-message {
-  background-color: #FFDD55;
+  background-color: #ffdd55;
   margin-left: 0; // 消息靠左显示
   margin-right: auto; // 重置右边距
   margin-bottom: rpx(10);

+ 6 - 6
vite.config.js

@@ -14,11 +14,11 @@ export default defineConfig({
   base: './',
   server: {
     host: "0.0.0.0",
-    // proxy: {
-    //   '/api': {
-    //     target: 'https://192.168.110.8:8080',
-    //     changeOrigin: true
-    //   }
-    // }
+    proxy: {
+      '/api': {
+        target: 'https://192.168.110.8:8080',
+        changeOrigin: true
+      }
+    }
   }
 })