liyanbo 6 месяцев назад
Родитель
Сommit
ae921486ff
1 измененных файлов с 1558 добавлено и 0 удалено
  1. 1558 0
      src/components/blockly/BlocklyEditor.vue

+ 1558 - 0
src/components/blockly/BlocklyEditor.vue

@@ -0,0 +1,1558 @@
+<template>
+  <div class="container">
+    <div class="content">
+      <div class="toolbox-section">
+        <h2>工具箱</h2>
+        <div id="toolbox" style="display: none;">
+
+          <!-- 添加AI模块分类 -->
+          <category name="AI模块" categorystyle="ai_category">
+            <block type="ai_voice_input"></block>
+            <block type="ai_text_to_image"></block>
+            <block type="ai_text_to_video"></block>
+            <block type="ai_text_to_text"></block>
+            <block type="ai_smart_lamp_single_param"></block>
+            <block type="ai_smart_lamp"></block>
+            <block type="ai_lamp_set_brightness"></block>
+            <block type="ai_lamp_set_color"></block>
+            <block type="ai_music_play"></block>
+          </category>
+
+          <category name="逻辑" colour="%{BKY_LOGIC_HUE}">
+            <block type="controls_if"></block>
+            <block type="logic_compare"></block>
+            <block type="logic_operation"></block>
+            <block type="logic_negate"></block>
+            <block type="logic_boolean"></block>
+          </category>
+          <category name="循环" colour="%{BKY_LOOPS_HUE}">
+            <block type="controls_repeat_ext">
+              <value name="TIMES">
+                <shadow type="math_number">
+                  <field name="NUM">10</field>
+                </shadow>
+              </value>
+            </block>
+            <block type="controls_whileUntil"></block>
+          </category>
+          <category name="数学" colour="%{BKY_MATH_HUE}">
+            <block type="math_number"></block>
+            <block type="math_arithmetic"></block>
+            <block type="math_single"></block>
+          </category>
+          <category name="文本" colour="%{BKY_TEXTS_HUE}">
+            <block type="text"></block>
+            <block type="text_length"></block>
+            <block type="text_print"></block>
+          </category>
+          <category name="变量" colour="%{BKY_VARIABLES_HUE}" custom="VARIABLE"></category>
+        </div>
+
+        <div class="json-section">
+          <h3>JSON 数据</h3>
+          <textarea v-model="jsonData" placeholder="在此输入JSON格式的积木块数据..."></textarea>
+          <div class="controls">
+            <button @click="loadWorkspaceFromJson">加载JSON到工作区</button>
+            <button @click="exportWorkspaceToJson">导出工作区为JSON</button>
+          </div>
+          <div v-if="statusMessage" :class="['status', statusType]">
+            {{ statusMessage }}
+          </div>
+        </div>
+
+      </div>
+
+      <div class="workspace-section">
+        <h2>工作区</h2>
+        <div id="blocklyDiv"></div>
+        <div class="controls">
+          <button id="generateCode" @click="generateCode">生成代码</button>
+          <button id="runCode" @click="runCode">运行代码</button>
+          <button @click="clearWorkspace">清空工作区</button>
+        </div>
+      </div>
+
+      <div class="output-section">
+        <h2>输出</h2>
+        <div class="controls">
+          <button @click="clearOutput">清空输出</button>
+        </div>
+        <pre id="output">{{ output }}</pre>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup>
+import { ref, onMounted, onUnmounted, reactive } from 'vue';
+import * as Blockly from "blockly";
+import 'blockly/msg/zh-hans';
+import { javascriptGenerator } from "blockly/javascript";
+import { pythonGenerator } from "blockly/python";
+
+// 【文生图】文生图
+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";
+//音乐
+import { playMusic, stopMusic, onMusicEnded } from "@/api/blockly/music.js";
+
+// 响应式变量
+const jsonData = ref(`{
+  "blocks": {
+    "languageVersion": 0,
+    "blocks": [
+      {
+        "type": "text_print",
+        "x": 100,
+        "y": 100,
+        "inputs": {
+          "TEXT": {
+            "block": {
+              "type": "text",
+              "fields": {
+                "TEXT": "Hello, Blockly!"
+              }
+            }
+          }
+        }
+      },
+      {
+        "type": "controls_if",
+        "x": 100,
+        "y": 200,
+        "inputs": {
+          "IF0": {
+            "block": {
+              "type": "logic_compare",
+              "fields": {
+                "OP": "EQ"
+              },
+              "inputs": {
+                "A": {
+                  "block": {
+                    "type": "math_number",
+                    "fields": {
+                      "NUM": 5
+                    }
+                  }
+                },
+                "B": {
+                  "block": {
+                    "type": "math_number",
+                    "fields": {
+                      "NUM": 5
+                    }
+                  }
+                }
+              }
+            }
+          },
+          "DO0": {
+            "block": {
+              "type": "text_print",
+              "inputs": {
+                "TEXT": {
+                  "block": {
+                    "type": "text",
+                    "fields": {
+                      "TEXT": "条件成立!"
+                    }
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    ]
+  }
+}`);
+
+const output = ref('// 生成的代码将显示在这里\n');
+const statusMessage = ref('');
+const statusType = ref('');
+let workspace = null;
+
+// 创建音乐播放器引用
+const musicPlayer = ref(null);
+
+// 状态管理
+const state = reactive({
+  workspace: null,
+  generatedContent: {
+    imageUrl: "",
+    videoUrl: "",
+    text: "",
+  },
+  previewVisible: false,
+  previewType: "",
+  previewContent: "",
+  isProcessing: false,
+
+  //年级
+  gradeId: "",
+
+  //【文生图】文生图
+  inProgressImageMap: {},
+
+  //【文生视频】文生视频
+  inProgressVideoMap: {},
+
+  // 台灯状态
+  lamp: {
+    isLightOn: false,// 台灯是否亮着
+    brightness: 0, // 默认亮度50%
+    color: "#ffffff", // 默认颜色白色
+    colorLog: "白", // 默认颜色白色
+  },
+
+  // 【文本文】对话相关状态
+  activeConversationId: null,
+  conversationInAbortController: null,
+
+  // 独立的音乐播放状态
+  currentMusicUrl: '',
+  currentMusicName: '',
+  isMusicPlaying: false,
+});
+
+// 统一轮询管理器
+const pollingManager = {
+  timers: {},
+
+  // 启动轮询
+  startPolling(type, callback, interval = 3000) {
+    // 如果已有相同类型的轮询,先清除
+    this.stopPolling(type);
+
+    this.timers[type] = setInterval(async () => {
+      try {
+        await callback();
+      } catch (error) {
+        console.error(`${type}轮询失败:`, error);
+      }
+    }, interval);
+
+    return this.timers[type];
+  },
+
+  // 停止轮询
+  stopPolling(type) {
+    if (this.timers[type]) {
+      clearInterval(this.timers[type]);
+      this.timers[type] = null;
+    }
+  },
+
+  // 停止所有轮询
+  stopAll() {
+    Object.keys(this.timers).forEach(type => this.stopPolling(type));
+  }
+};
+
+// 统一的错误处理包装器
+function withErrorHandling(operationName, fn, errorMessage = null) {
+  return async function(...args) {
+    try {
+      state.isProcessing = true;
+      return await fn.apply(this, args);
+    } catch (error) {
+      console.error(`${operationName}失败:`, error);
+      showStatus(errorMessage || `${operationName}发生错误: ${error.message || '未知错误'}`);
+      return null;
+    } finally {
+      state.isProcessing = false;
+    }
+  };
+}
+
+// 任务状态轮询公共函数
+async function pollTaskStatus(taskType, taskIds, fetchApi, onSuccess, onFailure) {
+  if (taskIds.length === 0) {
+    pollingManager.stopPolling(taskType);
+    return {};
+  }
+
+  try {
+    const list = await fetchApi(taskIds);
+    const activeTasks = {};
+
+    list.data.forEach((task) => {
+      if (task.status === AiImageStatusEnum.IN_PROGRESS) {
+        activeTasks[task.id] = task;
+      } else if (task.status === AiImageStatusEnum.SUCCESS) {
+        // 任务成功完成
+        if (onSuccess) {
+          onSuccess(task);
+        }
+      } else if (task.status === AiImageStatusEnum.FAIL) {
+        // 任务失败
+        if (onFailure) {
+          onFailure(task);
+        }
+      }
+    });
+
+    return activeTasks;
+  } catch (error) {
+    console.error(`${taskType}状态轮询失败:`, error);
+    return {};
+  }
+}
+
+// AI服务模块 - 统一管理
+const aiService = {
+  // 语音识别
+  recognizeVoice: withErrorHandling('语音识别', async function(promptText = "", language = "zh-CN") {
+    console.log("语音识别开始");
+    // 前端语音采集
+    const recognitionResult = await this.captureVoice(language, promptText);
+    return recognitionResult || "";
+  }, '语音识别失败'),
+
+  // 前端语音采集
+  captureVoice(language, promptText) {
+    return new Promise((resolve) => {
+      if (
+          !"webkitSpeechRecognition" in window &&
+          !"SpeechRecognition" in window
+      ) {
+        showStatus("您的浏览器不支持语音识别功能");
+        resolve("");
+        return;
+      }
+
+      const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
+      const recognition = new SpeechRecognition();
+
+      recognition.lang = language;
+      recognition.interimResults = false;
+      recognition.maxAlternatives = 1;
+
+      let countdown = 10;
+
+      // 固定的消息提示框
+      const messageText = promptText ? `${promptText}\n请开始说话...(${countdown}秒)` : `请开始说话...(${countdown}秒)`;
+      showStatus(messageText);
+
+      // 倒计时
+      const timer = setInterval(() => {
+        countdown--;
+        if (countdown > 0) {
+          const newText = promptText ? `${promptText}\n请开始说话...(${countdown}秒)` : `请开始说话...(${countdown}秒)`;
+          showStatus(newText);
+        } else {
+          clearInterval(timer);
+        }
+      }, 1000);
+
+      recognition.onresult = (event) => {
+        const speechResult = event.results[0][0].transcript;
+        console.log("语音识别结果:", speechResult);
+        showStatus("语音识别完成");
+        resolve(speechResult);
+      };
+
+      recognition.onerror = (event) => {
+        console.error("语音识别错误:", event.error);
+        showStatus("语音识别发生错误: " + event.error, 'error');
+        resolve("");
+      };
+
+      recognition.start();
+    });
+  },
+
+  // 文本生成图片
+  textToImage: withErrorHandling('AI图片生成', async function(prompt, waitForCompletion = true) {
+    console.log("AI图片生成中,提示词:", prompt);
+
+    //获取文生图-模型id
+    const modelRes = await getModelIdByType({
+      type: ModelTypeEnum.TEXT_TO_IMAGE,
+      platform: ModelPlatformEnum.DOUBAO,
+    });
+    if (!modelRes.data) {
+      showStatus("获取模型ID失败", 'error');
+      return null;
+    }
+
+    // 使用CreatePainting API创建图片任务
+    const createRes = await CreatePainting({
+      modelId: modelRes.data,
+      prompt: prompt,
+      width: 1024,
+      height: 1024,
+    });
+
+    // 记录任务ID到映射中
+    state.inProgressImageMap[createRes.data] = {
+      id: createRes.data,
+      status: AiImageStatusEnum.IN_PROGRESS,
+    };
+
+    // 开始轮询任务状态
+    this.startPollingTasks('image');
+
+    // 如果需要等待完成,等待图片生成完成
+    if (waitForCompletion) {
+      console.log("AI图片生成中,请等待。。。:");
+      return await this.waitForImageCompletion(createRes.data);
+    }
+
+    return createRes.data; // 返回任务ID
+  }, '生成图片失败'),
+
+  // 【文生图】等待图片生成完成
+  waitForImageCompletion(imageId) {
+    return new Promise((resolve, reject) => {
+      const checkInterval = setInterval(async () => {
+        try {
+          const list = await PaintingGetMys([imageId]);
+          if (list.data && list.data.length > 0) {
+            const image = list.data[0];
+            if (image.status === AiImageStatusEnum.SUCCESS) {
+              clearInterval(checkInterval);
+              resolve(image.picUrl);
+            } else if (image.status === AiImageStatusEnum.FAIL) {
+              clearInterval(checkInterval);
+              reject(new Error(image.error || "图片生成失败"));
+            }
+          }
+        } catch (error) {
+          clearInterval(checkInterval);
+          reject(error);
+        }
+      }, 3000);
+    });
+  },
+
+  // 文本生成视频
+  textToVideo: withErrorHandling('AI视频生成', async function(prompt, waitForCompletion = true) {
+    console.log("AI视频生成中,提示词:", prompt);
+
+    //获取视频生成模型id
+    const modelRes = await getModelIdByType({
+      type: ModelTypeEnum.IMAGE_TO_VIDEO,
+      platform: ModelPlatformEnum.DOUBAO,
+    });
+
+    if (!modelRes.data) {
+      showStatus("获取模型ID失败", 'error');
+      return null;
+    }
+
+    // 使用CreateVideo API创建视频任务
+    const createRes = await CreateVideo({
+      modelId: modelRes.data,
+      prompt: prompt,
+      duration: 4,
+      resolution: "1080P",
+    });
+
+    // 记录任务ID
+    state.inProgressVideoMap[createRes.data] = {
+      id: createRes.data,
+      status: AiImageStatusEnum.IN_PROGRESS,
+    };
+
+    console.log("AI视频生成中,请等待。。。");
+    // 启动统一的轮询机制
+    this.startPollingTasks('video');
+
+    // 如果需要等待完成,使用Promise封装结果
+    if (waitForCompletion) {
+      return new Promise((resolve, reject) => {
+        // 设置一次性的状态检查
+        const checkStatus = () => {
+          const videoInfo = state.generatedContent.videoUrl;
+          if (videoInfo && videoInfo.includes(createRes.data)) {
+            resolve(videoInfo);
+          } else if (state.inProgressVideoMap[createRes.data]?.status === AiImageStatusEnum.FAIL) {
+            reject(new Error("视频生成失败"));
+          } else if (!state.inProgressVideoMap[createRes.data]) {
+            reject(new Error("视频任务已不存在"));
+          } else {
+            // 继续检查
+            setTimeout(checkStatus, 1000);
+          }
+        };
+        checkStatus();
+      });
+    }
+
+    return createRes.data; // 返回任务ID
+  }, '生成视频失败'),
+
+  // 文本生成文本(如AI对话)
+  textToText: withErrorHandling('AI大模型调用', async function(prompt, model = "default") {
+    console.log("AI智能体请求,输入文本:", prompt);
+
+    // 如果没有活跃的对话ID,创建新对话
+    if (!state.activeConversationId) {
+      // 使用与TextToText.vue相同的方式创建对话
+      const res = await CreateDialogue({ roleId: 75 });
+      state.activeConversationId = res.data;
+      console.log("AI智能体创建成功,请等待。。。");
+    }
+
+    // 创建AbortController实例
+    state.conversationInAbortController = new AbortController();
+
+    // 使用流式API发送消息
+    let resultText = "";
+    let isFirstChunk = 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) {
+      console.log("AI大模型调用成功,返回结果:", resultText);
+      state.generatedContent.text = resultText;
+      state.previewType = "text";
+      state.previewContent = resultText;
+      if (!state.previewVisible) {
+        state.previewVisible = true;
+      }
+    }
+
+    return resultText;
+  }, 'AI大模型调用失败'),
+
+  // 停止文本生成流
+  stopTextToTextStream() {
+    if (state.conversationInAbortController) {
+      state.conversationInAbortController.abort();
+    }
+  },
+
+  // 设置台灯亮度
+  setLampBrightness: withErrorHandling('设置台灯亮度', async function(brightness) {
+    // 验证亮度值在0-100之间
+    const validBrightness = Math.max(0, Math.min(100, parseInt(brightness) || 0));
+
+    // 更新状态
+    state.lamp.brightness = validBrightness;
+
+    // 模拟API调用(实际项目中可替换为真实API)
+    console.log(`智能台灯亮度已设置为: ${validBrightness}%`);
+
+    return validBrightness;
+  }, '设置台灯亮度失败'),
+
+  // 设置台灯颜色
+  setLampColor: withErrorHandling('设置台灯颜色', async function(color) {
+    // 预定义的颜色映射
+    const colorMap = {
+      '紫': '#D886F0',
+      '橙': '#F89E35',
+      '黄': '#F9E67E',
+      '青': '#6BF5E6',
+      '白': '#ffffff',
+    };
+
+    // 获取有效的颜色值
+    let validColor = colorMap[color] || color;
+
+    // 检查是否是有效的颜色格式
+    if (!/^#[0-9A-F]{6}$/i.test(validColor)) {
+      validColor = "#ffffff"; // 默认白色
+    }
+
+    // 更新状态
+    state.lamp.color = validColor;
+    state.lamp.colorLog = color;
+
+    // 模拟API调用(实际项目中可替换为真实API)
+    console.log(`智能台灯颜色已设置为: ${color}`);
+
+    return validColor;
+  }, '设置台灯颜色失败'),
+
+  // 音乐播放相关方法
+  playMusic: withErrorHandling('播放音乐', async function(musicType) {
+    return playMusic(musicType, state, musicPlayer);
+  }, '播放音乐失败'),
+
+  stopMusic: withErrorHandling('停止音乐', async function() {
+    return stopMusic(state, musicPlayer);
+  }, '停止音乐失败'),
+
+  // 综合控制台灯(参数格式:"颜色, 亮度, 音乐")
+  controlLampWithSingleParam: withErrorHandling('智能台灯综合控制', async function(params) {
+    // 解析参数字符串
+    let color = '白'; // 默认颜色
+    let brightness = 0; // 默认亮度
+    let music = ''; // 音乐信息
+
+    if (params && typeof params === 'string') {
+      // 根据逗号分割参数
+      const paramArray = params.split(',').map(p => p.trim());
+
+      // 提取颜色(第一个参数)
+      if (paramArray.length > 0 && paramArray[0]) {
+        color = paramArray[0];
+      }
+
+      // 提取亮度(第二个参数)
+      if (paramArray.length > 1 && paramArray[1]) {
+        brightness = paramArray[1];
+      }
+
+      // 提取音乐(第三个参数)
+      if (paramArray.length > 2 && paramArray[2]) {
+        music = paramArray[2];
+
+        // 调用音乐播放函数
+        await this.playMusic(music);
+      }
+    }
+    // 调用控制台灯方法
+    return await this.controlLamp(brightness, color);
+  }, '智能台灯综合控制失败'),
+
+  // 综合控制台灯(参数格式:"颜色, 亮度")
+  controlLamp: withErrorHandling('智能台灯控制', async function(brightness, color) {
+    // 先设置亮度
+    await this.setLampBrightness(brightness);
+
+    // 再设置颜色
+    await this.setLampColor(color);
+
+    return { brightness: state.lamp.brightness, color: state.lamp.color };
+  }, '智能台灯控制失败'),
+
+  // 启动任务轮询
+  startPollingTasks(type) {
+    if (type === 'image' || type === 'all') {
+      pollingManager.startPolling('image', async () => {
+        const imageIds = Object.keys(state.inProgressImageMap).map(Number);
+        state.inProgressImageMap = await pollTaskStatus(
+            'image',
+            imageIds,
+            PaintingGetMys,
+            (image) => {
+              state.generatedContent.imageUrl = image.picUrl;
+              state.previewType = "image";
+              state.previewContent = image.picUrl;
+              state.previewVisible = true;
+              console.log("AI图片生成完成:", image.picUrl);
+            },
+            (image) => {
+              showStatus("图片生成失败: " + (image.error || "未知错误"), 'error');
+              console.error("图片生成失败:", image.id, image.error);
+            }
+        );
+      });
+    }
+
+    if (type === 'video' || type === 'all') {
+      pollingManager.startPolling('video', async () => {
+        const videoIds = Object.keys(state.inProgressVideoMap).map(Number);
+        state.inProgressVideoMap = await pollTaskStatus(
+            'video',
+            videoIds,
+            VideoGetMys,
+            (video) => {
+              state.generatedContent.videoUrl = video.videoUrl;
+              state.previewType = "video";
+              state.previewContent = video.videoUrl;
+              state.previewVisible = true;
+              console.log("AI视频生成完成:", video.videoUrl);
+            },
+            (video) => {
+              showStatus("视频生成失败: " + (video.error || "未知错误"), 'error');
+              console.error("视频生成失败:", video.id, video.error);
+            }
+        );
+      });
+    }
+  }
+};
+
+// 音乐相关的处理函数
+const handleMusicEnded = () => {
+  onMusicEnded(state);
+};
+
+// 专门的停止音乐处理函数
+const handleStopMusic = () => {
+  // 直接调用导入的stopMusic函数并传递正确的参数
+  stopMusic(state, musicPlayer);
+  // 提示信息
+  showStatus('音乐已停止播放');
+};
+
+// 关闭预览
+const handleClosePreview = () => {
+  state.previewVisible = false;
+  state.previewContent = "";
+  state.previewType = "";
+  state.generatedContent.text = null;
+  state.generatedContent.imageUrl = null;
+  state.generatedContent.videoUrl = null;
+};
+
+// 组件挂载后初始化Blockly
+onMounted(() => {
+  // 从全局状态初始化年级ID
+  state.gradeId = globalState.initGradeId();
+
+  // 注册AI语音输入积木
+  Blockly.Blocks["ai_voice_input"] = {
+    init: function () {
+      this.appendDummyInput().appendField("语音输入");
+      this.appendValueInput("PROMPT")
+          .setCheck("String")
+          .appendField("提示文字:");
+      this.appendDummyInput()
+          .appendField("语言:")
+          .appendField(
+              new Blockly.FieldDropdown([
+                ["中文", "zh-CN"],
+                ["英文", "en-US"],
+              ]),
+              "LANGUAGE"
+          );
+      this.setOutput(true, "String");
+      this.setColour(310);
+      this.setTooltip("使用语音识别获取文本输入");
+      this.setHelpUrl("");
+    },
+  };
+
+  // 注册AI文本生成图片积木
+  Blockly.Blocks["ai_text_to_image"] = {
+    init: function () {
+      this.appendDummyInput().appendField("AI生成图片");
+      this.appendValueInput("PROMPT").setCheck("String").appendField("提示词:");
+      this.appendDummyInput()
+          .appendField("等待完成:", "WAIT_LABEL")
+          .appendField(new Blockly.FieldCheckbox("TRUE"), "WAIT_FOR_COMPLETION");
+      this.setInputsInline(false);
+      this.setPreviousStatement(true, null);
+      this.setNextStatement(true, null);
+      this.setColour(340);
+      this.setTooltip("使用AI将文本描述转换为图片");
+      this.setHelpUrl("");
+    },
+  };
+
+  // 注册AI文本生成视频积木
+  Blockly.Blocks["ai_text_to_video"] = {
+    init: function () {
+      this.appendDummyInput().appendField("AI生成视频");
+      this.appendValueInput("PROMPT").setCheck("String").appendField("提示词:");
+      this.appendDummyInput()
+          .appendField("等待完成:", "WAIT_LABEL")
+          .appendField(new Blockly.FieldCheckbox("TRUE"), "WAIT_FOR_COMPLETION");
+      this.setInputsInline(false);
+      this.setPreviousStatement(true, null);
+      this.setNextStatement(true, null);
+      this.setColour(340);
+      this.setTooltip("使用AI将文本描述转换为视频");
+      this.setHelpUrl("");
+    },
+  };
+
+  // 注册AI文本生成文本积木
+  Blockly.Blocks["ai_text_to_text"] = {
+    init: function () {
+      this.appendDummyInput().appendField("AI大模型调用");
+      this.appendValueInput("PROMPT")
+          .setCheck("String")
+          .appendField("输入文本:");
+      this.appendValueInput("提示词")
+          .setCheck("String")
+          .appendField("提示词:");
+      this.setOutput(true, "String");
+      this.setColour(300);
+      this.setTooltip("使用AI大模型调用并返回结果");
+      this.setHelpUrl("");
+    },
+  };
+
+  //AI智能台灯单参数积木
+  Blockly.Blocks['ai_smart_lamp_single_param'] = {
+    init: function() {
+      this.appendDummyInput()
+          .appendField('智能台灯控制(单参数)');
+      this.appendValueInput('PARAMS')
+          .setCheck('String')
+          .appendField('参数(格式: 颜色,亮度,音乐):');
+      this.appendDummyInput()
+          .appendField('例如: 蓝,50,平静');
+      this.setInputsInline(false);
+      this.setPreviousStatement(true, null);
+      this.setNextStatement(true, null);
+      this.setColour(280);
+      this.setTooltip('通过一个参数字符串控制智能台灯的亮度、颜色和音乐\n格式: 颜色,亮度,音乐\n例如: 蓝,50,平静');
+      this.setHelpUrl('');
+    }
+  };
+
+  // 注册AI智能台灯积木
+  Blockly.Blocks["ai_smart_lamp"] = {
+    init: function () {
+      this.appendDummyInput().appendField("智能台灯控制");
+      this.appendValueInput("BRIGHTNESS")
+          .setCheck(["Number", "String"])
+          .appendField("亮度 (0-100):");
+      this.appendValueInput("COLOR").setCheck("String").appendField("颜色:");
+      this.setInputsInline(false);
+      this.setPreviousStatement(true, null);
+      this.setNextStatement(true, null);
+      this.setColour(280);
+      this.setTooltip("控制智能台灯的亮度和颜色");
+      this.setHelpUrl("");
+    },
+  };
+
+  // 注册AI台灯设置亮度积木
+  Blockly.Blocks["ai_lamp_set_brightness"] = {
+    init: function () {
+      this.appendDummyInput().appendField("设置台灯亮度");
+      this.appendValueInput("BRIGHTNESS")
+          .setCheck(["Number", "String"])
+          .appendField("亮度 (0-100):");
+      this.setInputsInline(false);
+      this.setPreviousStatement(true, null);
+      this.setNextStatement(true, null);
+      this.setColour(270);
+      this.setTooltip("设置智能台灯的亮度");
+      this.setHelpUrl("");
+    },
+  };
+
+  // 注册AI台灯设置颜色积木
+  Blockly.Blocks["ai_lamp_set_color"] = {
+    init: function () {
+      this.appendDummyInput().appendField("设置台灯颜色");
+      this.appendValueInput("COLOR").setCheck("String").appendField("颜色:");
+      this.appendDummyInput().appendField("可选颜色: 红,蓝,绿,黄,青,靛,紫");
+      this.setInputsInline(false);
+      this.setPreviousStatement(true, null);
+      this.setNextStatement(true, null);
+      this.setColour(275);
+      this.setTooltip("设置智能台灯的颜色");
+      this.setHelpUrl("");
+    },
+  };
+
+  // 注册音乐播放积木
+  Blockly.Blocks['ai_music_play'] = {
+    init: function() {
+      this.appendDummyInput()
+          .appendField('播放音乐');
+      this.appendDummyInput()
+          .appendField('音乐类型:')
+          .appendField(
+              new Blockly.FieldDropdown([
+                ['热闹', '热闹'],
+                ['舒缓', '舒缓'],
+              ]),
+              'MUSIC_TYPE'
+          );
+      this.setInputsInline(false);
+      this.setPreviousStatement(true, null);
+      this.setNextStatement(true, null);
+      this.setColour(290);
+      this.setTooltip('播放指定类型的音乐');
+      this.setHelpUrl('');
+    }
+  };
+
+  // 注册JavaScript代码生成器
+  registerJavaScriptGenerators();
+
+  // 注册Python代码生成器
+  registerPythonGenerators();
+
+  // 初始化Blockly工作区
+  const blocklyDiv = document.getElementById('blocklyDiv');
+  const toolbox = document.getElementById('toolbox');
+
+  workspace = Blockly.inject(blocklyDiv, {
+    toolbox: toolbox,
+    collapse: true,
+    comments: true,
+    disable: false, // 设为false以允许编辑
+    maxBlocks: Infinity,
+    trashcan: true,
+    horizontalLayout: false,
+    toolboxPosition: 'start',
+    css: true,
+    media: 'https://unpkg.com/blockly/media/',
+    rtl: false,
+    scrollbars: true,
+    sounds: false, // 禁用声音以提高性能
+    oneBasedIndex: true,
+    grid: {
+      spacing: 20,
+      length: 3,
+      colour: "#ccc",
+      snap: true
+    },
+    zoom: {
+      controls: true,
+      wheel: true,
+      startScale: 1.0,
+      maxScale: 3,
+      minScale: 0.3,
+      scaleSpeed: 1.2
+    }
+  });
+
+  // 使用state.workspace替代workspace
+  state.workspace = workspace;
+
+  // 加载初始JSON数据
+  loadWorkspaceFromJson();
+
+  // 修改工作区变化监听器,使其包含拖拽修复逻辑
+  workspace.addChangeListener((event) => {
+    // 生成代码
+    generateCode("javascript");
+
+    // 拖拽修复逻辑
+    if (event.type === Blockly.Events.BLOCK_CREATE) {
+      const block = workspace.getBlockById(event.blockId);
+      if (block) {
+        block.setEditable(true);
+      }
+    }
+  });
+
+  // 将aiService挂载到window,以便执行生成的代码时可以访问
+  window.aiService = aiService;
+});
+
+// 组件卸载时清除所有资源
+onUnmounted(() => {
+  // 统一的轮询管理器停止所有轮询
+  pollingManager.stopAll();
+
+  // 停止音乐播放
+  aiService.stopMusic();
+
+  // 停止文本生成流
+  aiService.stopTextToTextStream();
+
+  // 释放工作区资源
+  if (workspace) {
+    workspace.dispose();
+  }
+});
+
+// 注册JavaScript代码生成器
+function registerJavaScriptGenerators() {
+  // 语音输入
+  javascriptGenerator.forBlock['ai_voice_input'] = function(block, generator) {
+    const prompt = generator.valueToCode(block, 'PROMPT', javascriptGenerator.ORDER_ATOMIC);
+    const language = block.getFieldValue('LANGUAGE');
+    const code = `await aiService.recognizeVoice(${prompt || "''"}, '${language}')`;
+    return [code, javascriptGenerator.ORDER_ATOMIC];
+  };
+
+  // 文本生成图片
+  javascriptGenerator.forBlock['ai_text_to_image'] = function(block, generator) {
+    const prompt = generator.valueToCode(block, 'PROMPT', javascriptGenerator.ORDER_ATOMIC);
+    const waitForCompletion = block.getFieldValue('WAIT_FOR_COMPLETION') === 'TRUE';
+    const code = `await aiService.textToImage(${prompt}, ${waitForCompletion});`;
+    return code;
+  };
+
+  // 文本生成视频
+  javascriptGenerator.forBlock['ai_text_to_video'] = function(block, generator) {
+    const prompt = generator.valueToCode(block, 'PROMPT', javascriptGenerator.ORDER_ATOMIC);
+    const waitForCompletion = block.getFieldValue('WAIT_FOR_COMPLETION') === 'TRUE';
+    const code = `await aiService.textToVideo(${prompt}, ${waitForCompletion});`;
+    return code;
+  };
+
+  // 文本生成文本
+  javascriptGenerator.forBlock['ai_text_to_text'] = function(block, generator) {
+    const prompt = generator.valueToCode(block, 'PROMPT', javascriptGenerator.ORDER_ATOMIC);
+    const model = block.getFieldValue('MODEL');
+    const code = `await aiService.textToText(${prompt}, '${model}')`;
+    return [code, javascriptGenerator.ORDER_ATOMIC];
+  };
+
+  // 智能台灯控制(单参数)
+  javascriptGenerator.forBlock['ai_smart_lamp_single_param'] = function(block, generator) {
+    const params = generator.valueToCode(block, 'PARAMS', javascriptGenerator.ORDER_ATOMIC);
+    const code = `await aiService.controlLampWithSingleParam(${params || "'白,0,平静'"});`;
+    return code;
+  };
+
+  // 智能台灯控制(多参数)
+  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 || '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 || '0'});`;
+    return code;
+  };
+
+  // 设置台灯颜色
+  javascriptGenerator.forBlock['ai_lamp_set_color'] = function(block, generator) {
+    const color = generator.valueToCode(block, 'COLOR', javascriptGenerator.ORDER_ATOMIC);
+    const code = `await aiService.setLampColor(${color || "'白'"});`;
+    return code;
+  };
+
+  // 音乐播放
+  javascriptGenerator.forBlock['ai_music_play'] = function(block, generator) {
+    const musicType = block.getFieldValue('MUSIC_TYPE');
+    const code = `await aiService.playMusic('${musicType}');`;
+    return code;
+  };
+}
+
+// 注册Python代码生成器
+function registerPythonGenerators() {
+  // 语音输入
+  pythonGenerator.forBlock['ai_voice_input'] = function(block, generator) {
+    const prompt = generator.valueToCode(block, 'PROMPT', pythonGenerator.ORDER_ATOMIC);
+    const language = block.getFieldValue('LANGUAGE');
+    const code = `ai_service.recognize_voice(${prompt || "''"}, '${language}')`;
+    return [code, pythonGenerator.ORDER_ATOMIC];
+  };
+
+  // 文本生成图片
+  pythonGenerator.forBlock['ai_text_to_image'] = function(block, generator) {
+    const prompt = generator.valueToCode(block, 'PROMPT', pythonGenerator.ORDER_ATOMIC);
+    const waitForCompletion = block.getFieldValue('WAIT_FOR_COMPLETION') === 'TRUE';
+    const code = `ai_service.text_to_image(${prompt}, ${waitForCompletion})\n`;
+    return code;
+  };
+
+  // 文本生成视频
+  pythonGenerator.forBlock['ai_text_to_video'] = function(block, generator) {
+    const prompt = generator.valueToCode(block, 'PROMPT', pythonGenerator.ORDER_ATOMIC);
+    const waitForCompletion = block.getFieldValue('WAIT_FOR_COMPLETION') === 'TRUE';
+    const code = `ai_service.text_to_video(${prompt}, ${waitForCompletion})\n`;
+    return code;
+  };
+
+  // 文本生成文本
+  pythonGenerator.forBlock['ai_text_to_text'] = function(block, generator) {
+    const prompt = generator.valueToCode(block, 'PROMPT', pythonGenerator.ORDER_ATOMIC);
+    const model = block.getFieldValue('MODEL');
+    const code = `ai_service.text_to_text(${prompt}, '${model}')`;
+    return [code, pythonGenerator.ORDER_ATOMIC];
+  };
+
+  // 智能台灯控制(单参数)
+  pythonGenerator.forBlock['ai_smart_lamp_single_param'] = function(block, generator) {
+    const params = generator.valueToCode(block, 'PARAMS', pythonGenerator.ORDER_ATOMIC);
+    const code = `ai_service.control_lamp_with_single_param(${params || "'白,0,平静'"})\n`;
+    return code;
+  };
+
+  // 智能台灯控制
+  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 || '0'}, ${color || "'白'"})\n`;
+    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 || '0'})\n`;
+    return code;
+  };
+
+  // 设置台灯颜色
+  pythonGenerator.forBlock['ai_lamp_set_color'] = function(block, generator) {
+    const color = generator.valueToCode(block, 'COLOR', pythonGenerator.ORDER_ATOMIC);
+    const code = `ai_service.set_lamp_color(${color || "'白'"})\n`;
+    return code;
+  };
+
+  // 音乐播放
+  pythonGenerator.forBlock['ai_music_play'] = function(block, generator) {
+    const musicType = block.getFieldValue('MUSIC_TYPE');
+    const code = `ai_service.play_music('${musicType}')\n`;
+    return code;
+  };
+}
+
+// 从JSON加载工作区
+const loadWorkspaceFromJson = () => {
+  try {
+    const json = JSON.parse(jsonData.value);
+    Blockly.serialization.workspaces.load(json, workspace);
+    showStatus('工作区已成功从JSON加载!');
+  } catch (error) {
+    showStatus('JSON解析错误: ' + error.message, 'error');
+    console.error('JSON解析错误:', error);
+  }
+};
+
+// 导出工作区为JSON
+const exportWorkspaceToJson = () => {
+  try {
+    const state = Blockly.serialization.workspaces.save(workspace);
+    jsonData.value = JSON.stringify(state, null, 2);
+    showStatus('工作区已成功导出为JSON!');
+  } catch (error) {
+    showStatus('导出错误: ' + error.message, 'error');
+    console.error('导出错误:', error);
+  }
+};
+
+// 生成代码
+const generateCode = (language = 'javascript') => {
+  try {
+    let generator;
+    if (language === "javascript") {
+      generator = javascriptGenerator;
+    } else if (language === "python") {
+      generator = pythonGenerator;
+    } else {
+      console.error("不支持的语言类型");
+      return;
+    }
+
+    const code = generator.workspaceToCode(workspace);
+    output.value = code;
+    showStatus('代码生成成功!');
+  } catch (error) {
+    output.value = '// 代码生成错误: ' + error.message;
+    showStatus('代码生成错误: ' + error.message, 'error');
+    console.error('代码生成错误:', error);
+  }
+};
+
+// 运行代码
+const runCode = async () => {
+  try {
+    const code = javascriptGenerator.workspaceToCode(workspace);
+    output.value = code + '\n\n// 执行结果:\n';
+
+    // 保存原始console方法
+    const originalConsoleLog = console.log;
+    const originalConsoleError = console.error;
+    const originalConsoleWarn = console.warn;
+
+    // 创建输出缓冲区
+    let outputBuffer = "";
+
+    // 重定义console方法
+    console.log = (...args) => {
+      const message = args.map(arg => typeof arg === 'object' ? JSON.stringify(arg) : arg).join(' ');
+      outputBuffer += message + '\n';
+      output.value = code + '\n\n// 执行结果:\n' + outputBuffer;
+      originalConsoleLog.apply(console, args);
+    };
+
+    console.error = (...args) => {
+      const message = args.map(arg => typeof arg === 'object' ? JSON.stringify(arg) : arg).join(' ');
+      outputBuffer += '// 错误: ' + message + '\n';
+      output.value = code + '\n\n// 执行结果:\n' + outputBuffer;
+      originalConsoleError.apply(console, args);
+    };
+
+    console.warn = (...args) => {
+      const message = args.map(arg => typeof arg === 'object' ? JSON.stringify(arg) : arg).join(' ');
+      outputBuffer += '// 警告: ' + message + '\n';
+      output.value = code + '\n\n// 执行结果:\n' + outputBuffer;
+      originalConsoleWarn.apply(console, args);
+    };
+
+    try {
+      // 添加安全检查
+      if (code.includes('eval(') || code.includes('Function(') ||
+          code.includes('document.write') || code.includes('window.location')) {
+        throw new Error('代码包含不安全的操作');
+      }
+
+      // 包装代码为异步函数执行,支持await
+      const wrappedCode = `(async () => { ${code} })()`;
+      await new Function(wrappedCode)();
+
+      output.value = code + '\n\n// 执行结果:\n' + outputBuffer;
+    } catch (error) {
+      outputBuffer += '\n// 执行错误: ' + error.message;
+      output.value = code + '\n\n// 执行结果:\n' + outputBuffer;
+    } finally {
+      // 恢复原始console方法
+      console.log = originalConsoleLog;
+      console.error = originalConsoleError;
+      console.warn = originalConsoleWarn;
+    }
+
+    showStatus('代码执行成功!');
+  } catch (error) {
+    output.value += '\n// 执行错误: ' + error.message;
+    showStatus('代码执行错误: ' + error.message, 'error');
+    console.error('代码执行错误:', error);
+  }
+};
+
+// 清空工作区
+const clearWorkspace = () => {
+  workspace.clear();
+  showStatus('工作区已清空!');
+};
+
+// 清空输出
+const clearOutput = () => {
+  output.value = '// 输出已清空\n';
+};
+
+// 显示状态消息
+const showStatus = (message, type = 'success') => {
+  statusMessage.value = message;
+  statusType.value = type;
+
+  // 3秒后自动清除状态消息
+  setTimeout(() => {
+    statusMessage.value = '';
+  }, 3000);
+};
+</script>
+
+<style scoped>
+* {
+  margin: 0;
+  padding: 0;
+  box-sizing: border-box;
+  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
+}
+
+.container {
+  margin: 0 auto;
+  width: 80%;
+  background: rgba(255, 255, 255, 0.95);
+  border-radius: 15px;
+  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
+  overflow: hidden;
+}
+
+.content {
+  display: flex;
+  flex-wrap: wrap;
+  min-height: 600px;
+}
+
+.toolbox-section {
+  flex: 1;
+  min-width: 250px;
+  background: #f8f9fa;
+  padding: 15px;
+  border-right: 1px solid #e0e0e0;
+  display: flex;
+  flex-direction: column;
+}
+
+.workspace-section {
+  flex: 3;
+  min-width: 400px;
+  padding: 15px;
+  position: relative;
+  height: 70%;
+}
+
+.output-section {
+  margin-top: 15px;
+  background: #2c3e50;
+  color: white;
+  padding: 15px;
+  border-radius: 8px;
+  border: 1px solid #34495e;
+  width: 100%;
+}
+
+/* AI模块样式 */
+[categorystyle="ai_category"] > .blocklyTreeRow {
+  background-color: #9c27b0 !important;
+}
+
+/* 预览样式 */
+.preview-modal {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background-color: rgba(0, 0, 0, 0.7);
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  z-index: 1000;
+}
+
+.preview-content {
+  background-color: white;
+  padding: 20px;
+  border-radius: 8px;
+  max-width: 80%;
+  max-height: 80%;
+  overflow: auto;
+  position: relative;
+}
+
+.close-button {
+  position: absolute;
+  top: 10px;
+  right: 10px;
+  font-size: 24px;
+  background: none;
+  border: none;
+  cursor: pointer;
+  color: #333;
+}
+
+.preview-image-container,
+.preview-video-container {
+  display: flex;
+  justify-content: center;
+}
+
+.preview-image,
+.preview-video {
+  max-width: 100%;
+  max-height: 60vh;
+  border-radius: 4px;
+}
+
+.preview-text-container {
+  max-height: 60vh;
+  overflow-y: auto;
+  padding: 10px;
+  background-color: #f5f5f5;
+  border-radius: 4px;
+  color: #333;
+}
+
+/* 文生图预览 */
+.extra-image-preview {
+  color: black;
+  margin-top: 10px;
+  padding: 10px;
+  border: 1px solid #ddd;
+  border-radius: 5px;
+  background-color: #f9f9f9;
+}
+
+.extra-preview-image {
+  max-width: 100%;
+  max-height: 400px;
+  border-radius: 4px;
+}
+
+/* 音乐播放器 */
+.music-player-container {
+  margin-top: 20px;
+  padding: 15px;
+  background-color: #f9f9f9;
+  border-radius: 8px;
+  border: 1px solid #e0e0e0;
+  color: #333;
+}
+
+.music-player-container h5 {
+  margin-top: 0;
+  margin-bottom: 10px;
+  color: #333;
+  font-size: 16px;
+}
+
+.music-player-container audio {
+  width: 100%;
+  margin-bottom: 10px;
+}
+
+.music-status {
+  font-size: 14px;
+  color: #666;
+  padding: 5px 0;
+}
+
+h2 {
+  margin-bottom: 15px;
+  color: #2c3e50;
+  border-bottom: 2px solid #3498db;
+  padding-bottom: 8px;
+}
+
+.output-section h2 {
+  color: white;
+  border-bottom: 2px solid #1abc9c;
+}
+
+#blocklyDiv {
+  height: 500px;
+  width: 100%;
+  background: white;
+  border: 1px solid #ddd;
+  border-radius: 8px;
+}
+
+.controls {
+  display: flex;
+  gap: 10px;
+  margin-top: 15px;
+  flex-wrap: wrap;
+}
+
+button {
+  padding: 10px 20px;
+  border: none;
+  border-radius: 5px;
+  background: #3498db;
+  color: white;
+  font-weight: bold;
+  cursor: pointer;
+  transition: all 0.3s ease;
+}
+
+button:hover {
+  background: #2980b9;
+  transform: translateY(-2px);
+  box-shadow: 0 5px 10px rgba(0, 0, 0, 0.1);
+}
+
+#generateCode {
+  background: #2ecc71;
+}
+
+#generateCode:hover {
+  background: #27ae60;
+}
+
+#runCode {
+  background: #e74c3c;
+}
+
+#runCode:hover {
+  background: #c0392b;
+}
+
+#output {
+  background: #1a2530;
+  padding: 10px;
+  border-radius: 5px;
+  min-height: 100px;
+  max-height: 300px;
+  margin-top: 10px;
+  font-family: 'Courier New', monospace;
+  white-space: pre-wrap;
+  overflow-y: auto;
+  font-size: 12px;
+}
+
+.json-section {
+  margin-top: 15px;
+  padding: 15px;
+  background: #f1f8ff;
+  border-radius: 8px;
+  border: 1px solid #d1e7ff;
+}
+
+.json-section h3 {
+  margin-bottom: 10px;
+  color: #2c3e50;
+}
+
+textarea {
+  width: 100%;
+  min-height: 120px;
+  padding: 10px;
+  border: 1px solid #ddd;
+  border-radius: 5px;
+  font-family: 'Courier New', monospace;
+  resize: vertical;
+}
+
+.status {
+  margin-top: 10px;
+  padding: 8px 12px;
+  border-radius: 4px;
+  font-weight: bold;
+}
+
+.success {
+  background: #d4edda;
+  color: #155724;
+  border: 1px solid #c3e6cb;
+}
+
+.error {
+  background: #f8d7da;
+  color: #721c24;
+  border: 1px solid #f5c6cb;
+}
+</style>