Kaynağa Gözat

1、blockly-台灯组件默认亮度为0
2、加入智能问答组件

liyanbo 7 ay önce
ebeveyn
işleme
e8ee58c8e2
2 değiştirilmiş dosya ile 252 ekleme ve 63 silme
  1. 6 1
      src/api/teachers.js
  2. 246 62
      src/views/Blockly.vue

+ 6 - 1
src/api/teachers.js

@@ -12,9 +12,14 @@ export function teacherList (data) {
 
 // 模型类型枚举
 export const ModelTypeEnum = {
+  TEXT_TO_IMAGE: 2,   // 文生图
   IMAGE_TO_IMAGE: 7, // 图生图
+  TEXT_TO_VIDEO: 4, // 文生视频
   IMAGE_TO_VIDEO: 4, // 图生视频
-  TEXT_TO_IMAGE: 2   // 文生图
+}
+// 模型类型枚举
+export const ModelPlatformEnum = {
+  DOUBAO: "DouBao",   // 豆包
 }
 
 // 获取模型ID

+ 246 - 62
src/views/Blockly.vue

@@ -357,11 +357,15 @@
           <h5>生成的图片:</h5>
           <img :src="state.generatedContent.imageUrl" class="extra-preview-image" alt="AI生成图片">
         </div>
+        <div v-if="state.generatedContent.videoUrl" class="extra-image-preview">
+          <h5>生成的视频:</h5>
+          <video :src="state.generatedContent.videoUrl" controls class="preview-video" alt="AI生成视频"></video>
+        </div>
 
         <!-- 添加台灯显示区域 -->
         <div class="lamp-preview-container">
           <h5>AI智能台灯</h5>
-          <div class="lamp-display" :style="{ filter: `brightness(${state.lamp.brightness / 50})` }">
+          <div class="lamp-display" :style="{ filter: `brightness(${state.lamp.brightness / 100})` }">
             <div class="lamp-image" :style="{ boxShadow: `0 0 40px 20px ${state.lamp.color}80` }"></div>
           </div>
           <div class="lamp-info">
@@ -404,8 +408,15 @@ import * as hans from 'blockly/msg/zh-hans'
 import { ElDialog, ElButton, ElMessage } from 'element-plus';
 
 //【文生图】文生图
-import {AiImageStatusEnum, CreatePainting, PaintingGetMys} from '@/api/questions.js'
-import { getModelIdByType } from '@/api/teachers.js'
+import {
+  AiImageStatusEnum,
+  CreatePainting,
+  PaintingGetMys,
+  CreateVideo,
+  VideoGetMys,
+  sendChatMessageStream, CreateDialogue
+} from '@/api/questions.js'
+import {getModelIdByType, ModelPlatformEnum} from '@/api/teachers.js'
 import { ModelTypeEnum } from '@/api/teachers.js'
 import { globalState } from '@/utils/globalState.js'
 
@@ -424,21 +435,33 @@ const state = reactive({
   previewContent: '',
   isProcessing: false,
 
-  //【文生图】文生图
+  //年级
   gradeId: '',
+
+  //【文生图】文生图
   inProgressImageMap: {},
 
+  //【文生视频】文生视频
+  inProgressVideoMap: {},
 
   // 台灯状态
   lamp: {
-    brightness: 50,  // 默认亮度50%
+    brightness: 0,  // 默认亮度50%
     color: '#ffffff' // 默认颜色白色
   },
+
+  // 【文本文】对话相关状态
+  activeConversationId: null,
+  conversationInProgress: false,
+  conversationInAbortController: null
 });
 
 // 【文生图】自动刷新image列表的定时器
 const inProgressTimer = ref();
 
+// 【文生视频】自动刷新video列表的定时器
+const inProgressVideoTimer = ref();
+
 // 初始化Blockly工作区和自定义积木
 onMounted(async () => {
   // 从全局状态初始化年级ID
@@ -556,7 +579,7 @@ onMounted(async () => {
       this.appendDummyInput()
           .appendField('智能台灯控制');
       this.appendValueInput('BRIGHTNESS')
-          .setCheck('Number')
+          .setCheck(['Number', 'String'])
           .appendField('亮度 (0-100):');
       this.appendValueInput('COLOR')
           .setCheck('String')
@@ -576,7 +599,7 @@ onMounted(async () => {
       this.appendDummyInput()
           .appendField('设置台灯亮度');
       this.appendValueInput('BRIGHTNESS')
-          .setCheck('Number')
+          .setCheck(['Number', 'String'])
           .appendField('亮度 (0-100):');
       this.setInputsInline(false);
       this.setPreviousStatement(true, null);
@@ -627,6 +650,10 @@ onUnmounted(() => {
   if (inProgressTimer.value) {
     clearInterval(inProgressTimer.value);
   }
+
+  if (inProgressVideoTimer.value) {
+    clearInterval(inProgressVideoTimer.value);
+  }
 });
 
 // AI服务配置
@@ -723,7 +750,7 @@ const aiService = {
     try {
 
       //获取文生图-模型id
-      const modelRes = await getModelIdByType({ type: ModelTypeEnum.TEXT_TO_IMAGE, platform: "DouBao" })
+      const modelRes = await getModelIdByType({ type: ModelTypeEnum.TEXT_TO_IMAGE, platform: ModelPlatformEnum.DOUBAO })
       if (!modelRes.data) {
         ElMessage.error('获取模型ID失败');
         return null;
@@ -825,10 +852,11 @@ const aiService = {
           const list = await PaintingGetMys([imageId]);
           if (list.data && list.data.length > 0) {
             const image = list.data[0];
-            if (image.status === AiImageStatusEnum.COMPLETED) {
+            console.log('图片状态:', image.status, image.picUrl, AiImageStatusEnum);
+            if (image.status === AiImageStatusEnum.SUCCESS) {
               clearInterval(checkInterval);
               resolve(image.picUrl);
-            } else if (image.status === AiImageStatusEnum.FAILED) {
+            } else if (image.status === AiImageStatusEnum.FAIL) {
               clearInterval(checkInterval);
               reject(new Error(image.error || '图片生成失败'));
             }
@@ -847,78 +875,219 @@ const aiService = {
 
     state.isProcessing = true;
     try {
-      // 视频生成通常耗时较长,这里使用轮询或WebSocket会更好
-      // 简单实现:先获取任务ID,再轮询结果
-      const initResponse = await fetch(`${aiServiceConfig.baseUrl}${aiServiceConfig.endpoints.textToVideo}`, {
-        method: 'POST',
-        headers: { 'Content-Type': 'application/json' },
-        body: JSON.stringify({ prompt })
+      //获取视频生成模型id
+      const modelRes = await getModelIdByType({ type: ModelTypeEnum.IMAGE_TO_VIDEO, platform: ModelPlatformEnum.DOUBAO })
+      if (!modelRes.data) {
+        ElMessage.error('获取模型ID失败');
+        return null;
+      }
+
+      // 使用CreateVideo API创建视频任务
+      const createRes = await CreateVideo({
+        "modelId": modelRes.data,
+        "prompt": prompt,
+        "duration": 4,
+        "resolution": "1080P"
       });
 
-      if (!initResponse.ok) throw new Error('初始化视频生成失败');
+      // 记录任务ID到视频专用映射中
+      state.inProgressVideoMap[createRes.data] = {id: createRes.data, status: AiImageStatusEnum.IN_PROGRESS};
+
+      // 开始视频专用的轮询任务状态
+      if (!inProgressVideoTimer.value) {
+        this.startVideoPolling();
+      }
+
+      // 如果需要等待完成,等待视频生成完成
+      if (waitForCompletion) {
+        return await this.waitForVideoCompletion(createRes.data);
+      }
+
+      return createRes.data; // 返回任务ID
+    } catch (error) {
+      console.error('生成视频失败:', error);
+      ElMessage.error('生成视频失败: ' + error.message);
+      return null;
+    } finally {
+      state.isProcessing = false;
+    }
+  },
 
-      const initData = await initResponse.json();
-      const taskId = initData.taskId;
+  // 【视频生成】开始轮询视频生成状态
+  startVideoPolling() {
+    if (inProgressVideoTimer.value) {
+      clearInterval(inProgressVideoTimer.value);
+    }
 
-      // 轮询结果
-      const checkResult = async () => {
-        const resultResponse = await fetch(`${aiServiceConfig.baseUrl}${aiServiceConfig.endpoints.textToVideo}/${taskId}`);
-        const resultData = await resultResponse.json();
+    inProgressVideoTimer.value = setInterval(async () => {
+      await this.refreshWatchVideos();
+    }, 3000); // 每3秒轮询一次
+  },
 
-        if (resultData.status === 'completed') {
-          state.generatedContent.videoUrl = resultData.videoUrl;
+// 【视频生成】轮询生成中的视频列表
+  async refreshWatchVideos() {
+    const videoIds = Object.keys(state.inProgressVideoMap).map(Number);
+    if (videoIds.length === 0) {
+      if (inProgressVideoTimer.value) {
+        clearInterval(inProgressVideoTimer.value);
+        inProgressVideoTimer.value = null;
+      }
+      return;
+    }
 
-          // 显示预览
+    try {
+      const list = await VideoGetMys(videoIds);
+      const newWatchVideos = {};
+
+      list.data.forEach((video) => {
+        if (video.status === AiImageStatusEnum.IN_PROGRESS) {
+          newWatchVideos[video.id] = video;
+        } else if (video.status === AiImageStatusEnum.SUCCESS) {
+          // 视频生成完成,更新状态并显示预览
+          state.generatedContent.videoUrl = video.videoUrl;
           state.previewType = 'video';
-          state.previewContent = resultData.videoUrl;
+          state.previewContent = video.videoUrl;
           state.previewVisible = true;
 
-          return resultData.videoUrl;
-        } else if (resultData.status === 'failed') {
-          throw new Error(resultData.error || '视频生成失败');
-        } else {
-          // 继续轮询
-          await new Promise(resolve => setTimeout(resolve, 3000));
-          return checkResult();
+          // 打印到控制台,便于在结果区域显示
+          console.log('视频生成完成:', video.videoUrl);
+        } else if (video.status === AiImageStatusEnum.FAIL) {
+          // 视频生成失败
+          ElMessage.error('视频生成失败: ' + (video.error || '未知错误'));
+          console.error('视频生成失败:', video.id, video.error);
         }
-      };
+      });
+
+      state.inProgressVideoMap = newWatchVideos;
 
-      return checkResult();
+      // 如果没有正在处理的视频,清除定时器
+      if (Object.keys(newWatchVideos).length === 0 && inProgressVideoTimer.value) {
+        clearInterval(inProgressVideoTimer.value);
+        inProgressVideoTimer.value = null;
+      }
     } catch (error) {
-      console.error('生成视频失败:', error);
-      ElMessage.error('生成视频失败: ' + error.message);
-      return null;
-    } finally {
-      state.isProcessing = false;
+      console.error('轮询视频状态失败:', error);
     }
   },
 
+  // 【视频生成】等待视频生成完成
+  async waitForVideoCompletion(videoId) {
+    return new Promise((resolve, reject) => {
+      const checkInterval = setInterval(async () => {
+        try {
+          const list = await VideoGetMys([videoId]);
+          if (list.data && list.data.length > 0) {
+            const video = list.data[0];
+            console.log('视频状态:', video.status, video.videoUrl);
+            if (video.status === AiImageStatusEnum.SUCCESS) {
+              clearInterval(checkInterval);
+              // 视频生成完成,设置预览状态
+              state.generatedContent.videoUrl = video.videoUrl;
+              state.previewType = 'video';
+              state.previewContent = video.videoUrl;
+              state.previewVisible = true;
+              resolve(video.videoUrl);
+            } else if (video.status === AiImageStatusEnum.FAIL) {
+              clearInterval(checkInterval);
+              reject(new Error(video.error || '视频生成失败'));
+            }
+          }
+        } catch (error) {
+          clearInterval(checkInterval);
+          reject(error);
+        }
+      }, 3000);
+    });
+  },
+
   // 文本生成文本(如AI对话)
   async textToText(prompt, model = 'default') {
-
     console.log('textToText', prompt, model);
     state.isProcessing = true;
+
     try {
-      const response = await fetch(`${aiServiceConfig.baseUrl}${aiServiceConfig.endpoints.textToText}`, {
-        method: 'POST',
-        headers: { 'Content-Type': 'application/json' },
-        body: JSON.stringify({
-          prompt,
-          model
-        })
-      });
+      // 如果没有活跃的对话ID,创建新对话
+      if (!state.activeConversationId) {
+        // 使用与TextToText.vue相同的方式创建对话
+        const res = await CreateDialogue({ roleId: 10 });
+        state.activeConversationId = res.data;
+        console.log("创建会话:", res.data);
+      }
 
-      if (!response.ok) throw new Error('AI处理失败');
+      // 创建AbortController实例
+      state.conversationInAbortController = new AbortController();
+      state.conversationInProgress = true;
 
-      const data = await response.json();
-      state.generatedContent.text = data.result;
+      // 使用流式API发送消息
+      let resultText = '';
+      let isFirstChunk = true;
 
-      // 显示预览
-      state.previewType = 'text';
-      state.previewContent = data.result;
-      state.previewVisible = true;
+      await sendChatMessageStream(
+          state.activeConversationId,
+          prompt,
+          null,
+          state.conversationInAbortController,
+          true, // 启用上下文
+          async (res) => {
+            try {
+              const { code, data, msg } = JSON.parse(res.data);
+              if (code !== 0) {
+                console.log(`对话异常! ${msg}`);
+                return;
+              }
+
+              // 根据事件类型处理
+              if (data.eventType === "TEXT") {
+                // 如果内容为空,就不处理
+                if (data.receive?.content === "") {
+                  return;
+                }
+
+                // 处理文本消息
+                resultText += data.receive.content;
+
+                // 首次返回时更新预览内容
+                if (isFirstChunk) {
+                  isFirstChunk = false;
+                  // 设置预览内容
+                  state.generatedContent.text = resultText;
+                  state.previewType = 'text';
+                  state.previewContent = resultText;
+                  if (!state.previewVisible) {
+                    state.previewVisible = true;
+                  }
+                } else {
+                  // 更新预览内容
+                  state.generatedContent.text = resultText;
+                  state.previewContent = resultText;
+                }
+              }
+            } catch (error) {
+              console.error('处理流式响应失败:', error);
+            }
+          },
+          (error) => {
+            console.log(`对话异常! ${error}`);
+            this.stopTextToTextStream();
+            throw error;
+          },
+          () => {
+            console.log(`结束对话!`);
+            this.stopTextToTextStream();
+          }
+      );
+
+      // 确保最终结果被设置
+      if (resultText) {
+        state.generatedContent.text = resultText;
+        state.previewType = 'text';
+        state.previewContent = resultText;
+        if (!state.previewVisible) {
+          state.previewVisible = true;
+        }
+      }
 
-      return data.result;
+      return resultText;
     } catch (error) {
       console.error('AI文本处理失败:', error);
       ElMessage.error('AI文本处理失败: ' + error.message);
@@ -928,6 +1097,14 @@ const aiService = {
     }
   },
 
+  // 停止文本生成流
+  stopTextToTextStream() {
+    if (state.conversationInAbortController) {
+      state.conversationInAbortController.abort();
+    }
+    state.conversationInProgress = false;
+  },
+
   // 语义识别分析
   async semanticAnalysis(text, type = 'general') {
     console.log('semanticAnalysis', text, type);
@@ -1079,14 +1256,14 @@ function registerJavaScriptGenerators() {
   javascriptGenerator.forBlock['ai_smart_lamp'] = function(block, generator) {
     const brightness = generator.valueToCode(block, 'BRIGHTNESS', javascriptGenerator.ORDER_ATOMIC);
     const color = generator.valueToCode(block, 'COLOR', javascriptGenerator.ORDER_ATOMIC);
-    const code = `await aiService.controlLamp(${brightness || '50'}, ${color || "'白'"});`;
+    const code = `await aiService.controlLamp(${brightness || '0'}, ${color || "'白'"});`;
     return code;
   };
 
   // 设置台灯亮度
   javascriptGenerator.forBlock['ai_lamp_set_brightness'] = function(block, generator) {
     const brightness = generator.valueToCode(block, 'BRIGHTNESS', javascriptGenerator.ORDER_ATOMIC);
-    const code = `await aiService.setLampBrightness(${brightness || '50'});`;
+    const code = `await aiService.setLampBrightness(${brightness || '0'});`;
     return code;
   };
 
@@ -1144,14 +1321,14 @@ function registerPythonGenerators() {
   pythonGenerator.forBlock['ai_smart_lamp'] = function(block, generator) {
     const brightness = generator.valueToCode(block, 'BRIGHTNESS', pythonGenerator.ORDER_ATOMIC);
     const color = generator.valueToCode(block, 'COLOR', pythonGenerator.ORDER_ATOMIC);
-    const code = `ai_service.control_lamp(${brightness || '50'}, ${color || "'白'"})`;
+    const code = `ai_service.control_lamp(${brightness || '0'}, ${color || "'白'"})`;
     return code;
   };
 
   // 设置台灯亮度
   pythonGenerator.forBlock['ai_lamp_set_brightness'] = function(block, generator) {
     const brightness = generator.valueToCode(block, 'BRIGHTNESS', pythonGenerator.ORDER_ATOMIC);
-    const code = `ai_service.set_lamp_brightness(${brightness || '50'})`;
+    const code = `ai_service.set_lamp_brightness(${brightness || '0'})`;
     return code;
   };
 
@@ -1296,6 +1473,9 @@ function load() {
   if (!input.value) return;
 
   try {
+    //关闭预览
+    handleClosePreview();
+
     const valid = saveIsValid(input.value);
     if (valid.json) {
       const parsedState = JSON.parse(input.value);
@@ -1343,6 +1523,9 @@ function handleClosePreview() {
   state.previewVisible = false;
   state.previewContent = '';
   state.previewType = '';
+  state.generatedContent.text = null;
+  state.generatedContent.imageUrl = null;
+  state.generatedContent.videoUrl = null;
 }
 
 // 将aiService挂载到window,以便执行生成的代码时可以访问
@@ -1466,6 +1649,7 @@ window.aiService = aiService;
 
 //【文生图预览】
 .extra-image-preview {
+  color: black;
   margin-top: 10px;
   padding: 10px;
   border: 1px solid #ddd;