|
|
@@ -1,1550 +1,1205 @@
|
|
|
<template>
|
|
|
- <!-- AI发展历程 -->
|
|
|
+ <!-- Blockly -->
|
|
|
<div class="home-container">
|
|
|
- <!-- 展开收起侧边栏 -->
|
|
|
- <div class="icon-expand">
|
|
|
- <el-icon
|
|
|
- @click="toggleDrawer"
|
|
|
- :style="{ color: drawerVisible ? 'white' : '#B6B0D8' }"
|
|
|
- >
|
|
|
- <component :is="drawerVisible ? Fold : Expand" />
|
|
|
- <!-- <Tickets /> -->
|
|
|
- </el-icon>
|
|
|
- </div>
|
|
|
-
|
|
|
- <transition name="drawer-slide">
|
|
|
- <div class="main-content" v-if="drawerVisible">
|
|
|
- <!-- 添加抽屉 -->
|
|
|
- <div class="drawer-box">
|
|
|
- <el-row class="tac">
|
|
|
- <el-col :span="12">
|
|
|
- <h3 class="mb-2">
|
|
|
- <el-icon><Memo /></el-icon>
|
|
|
- 课程小节
|
|
|
- </h3>
|
|
|
- <el-menu
|
|
|
- :default-active="currentIndex"
|
|
|
- @open="handleOpen"
|
|
|
- @close="handleClose"
|
|
|
- @select="handleSelect"
|
|
|
- :default-openeds="['1']"
|
|
|
- >
|
|
|
- <template v-for="item in menuItems" :key="item.index">
|
|
|
- <el-menu-item v-if="!item.children" :index="item.index">{{
|
|
|
- item.title
|
|
|
- }}</el-menu-item>
|
|
|
- <el-sub-menu v-else :index="item.index">
|
|
|
- <template #title>
|
|
|
- <span>{{ item.title }}</span>
|
|
|
- </template>
|
|
|
- <el-menu-item-group v-if="item.children">
|
|
|
- <template
|
|
|
- v-for="child in item.children"
|
|
|
- :key="child.index"
|
|
|
- >
|
|
|
- <el-menu-item :index="child.index">{{
|
|
|
- child.title
|
|
|
- }}</el-menu-item>
|
|
|
- </template>
|
|
|
- </el-menu-item-group>
|
|
|
- </el-sub-menu>
|
|
|
- </template>
|
|
|
- </el-menu>
|
|
|
- </el-col>
|
|
|
- </el-row>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </transition>
|
|
|
-
|
|
|
- <div class="content-box">
|
|
|
- <div class="box-1">
|
|
|
- <div class="inner-box left-box">
|
|
|
- <div class="box-icon" @click="goBack">
|
|
|
- <el-icon class="left-icon"><ArrowLeftBold /></el-icon>
|
|
|
- {{ boxIconTitle }}
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div class="inner-box right-box">
|
|
|
- <div class="top-right-box">
|
|
|
- <el-input
|
|
|
- v-model="SearchInput"
|
|
|
- class="search-input"
|
|
|
- placeholder="搜索"
|
|
|
- >
|
|
|
- <template #prefix>
|
|
|
- <el-icon class="el-input__icon"><search /></el-icon>
|
|
|
- </template>
|
|
|
- </el-input>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- <div class="box-2">
|
|
|
- <!-- 课程标题 autoplay自动播放 @ended="playNextVideo"-->
|
|
|
- <div class="small-title">
|
|
|
- <span>{{ smallTitle }}</span>
|
|
|
- </div>
|
|
|
- <div class="box-blockly">
|
|
|
- <div id="blocklyDiv" style="height: 100%; width: 100%; background-color: #cccccc"></div>
|
|
|
-
|
|
|
- <!-- blockly工具栏 -->
|
|
|
- <!-- 使用 template 标签包裹 XML 内容 -->
|
|
|
- <template >
|
|
|
- <xml id="toolbox" style="display: none">
|
|
|
-<!-- <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>
|
|
|
- <sep></sep>
|
|
|
- <category name="数学" colour="%{BKY_MATH_HUE}">
|
|
|
- <block type="math_number">
|
|
|
- <field name="NUM">123</field>
|
|
|
- </block>
|
|
|
- <block type="math_number">
|
|
|
- <field name="NUM">213312</field>
|
|
|
- </block>
|
|
|
- <block type="math_arithmetic"></block>
|
|
|
- <block type="math_single"></block>
|
|
|
- </category>-->
|
|
|
- <category name="逻辑" categorystyle="logic_category">
|
|
|
- <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>
|
|
|
- <block type="logic_null" disabled="true"></block>
|
|
|
- <block type="logic_ternary"></block>
|
|
|
- </category>
|
|
|
- <category name="循环" categorystyle="loop_category">
|
|
|
- <block type="controls_repeat_ext">
|
|
|
- <value name="TIMES">
|
|
|
- <shadow type="math_number">
|
|
|
- <field name="NUM">10</field>
|
|
|
- </shadow>
|
|
|
- </value>
|
|
|
- </block>
|
|
|
- <block type="controls_repeat" disabled="true"></block>
|
|
|
- <block type="controls_whileUntil"></block>
|
|
|
- <block type="controls_for">
|
|
|
- <value name="FROM">
|
|
|
- <shadow type="math_number">
|
|
|
- <field name="NUM">1</field>
|
|
|
- </shadow>
|
|
|
- </value>
|
|
|
- <value name="TO">
|
|
|
- <shadow type="math_number">
|
|
|
- <field name="NUM">10</field>
|
|
|
- </shadow>
|
|
|
- </value>
|
|
|
- <value name="BY">
|
|
|
- <shadow type="math_number">
|
|
|
- <field name="NUM">1</field>
|
|
|
- </shadow>
|
|
|
- </value>
|
|
|
- </block>
|
|
|
- <block type="controls_forEach"></block>
|
|
|
- <block type="controls_flow_statements"></block>
|
|
|
- </category>
|
|
|
- <category name="数值" categorystyle="math_category">
|
|
|
- <block type="math_number" gap="32">
|
|
|
- <field name="NUM">123</field>
|
|
|
- </block>
|
|
|
- <block type="math_arithmetic">
|
|
|
- <value name="A">
|
|
|
- <shadow type="math_number">
|
|
|
- <field name="NUM">1</field>
|
|
|
- </shadow>
|
|
|
- </value>
|
|
|
- <value name="B">
|
|
|
- <shadow type="math_number">
|
|
|
- <field name="NUM">1</field>
|
|
|
- </shadow>
|
|
|
- </value>
|
|
|
- </block>
|
|
|
- <block type="math_single">
|
|
|
- <value name="NUM">
|
|
|
- <shadow type="math_number">
|
|
|
- <field name="NUM">9</field>
|
|
|
- </shadow>
|
|
|
- </value>
|
|
|
- </block>
|
|
|
- <block type="math_trig">
|
|
|
- <value name="NUM">
|
|
|
- <shadow type="math_number">
|
|
|
- <field name="NUM">45</field>
|
|
|
- </shadow>
|
|
|
- </value>
|
|
|
- </block>
|
|
|
- <block type="math_constant"></block>
|
|
|
- <block type="math_number_property">
|
|
|
- <value name="NUMBER_TO_CHECK">
|
|
|
- <shadow type="math_number">
|
|
|
- <field name="NUM">0</field>
|
|
|
- </shadow>
|
|
|
- </value>
|
|
|
- </block>
|
|
|
- <block type="math_round">
|
|
|
- <value name="NUM">
|
|
|
- <shadow type="math_number">
|
|
|
- <field name="NUM">3.1</field>
|
|
|
- </shadow>
|
|
|
- </value>
|
|
|
- </block>
|
|
|
- <block type="math_on_list"></block>
|
|
|
- <block type="math_modulo">
|
|
|
- <value name="DIVIDEND">
|
|
|
- <shadow type="math_number">
|
|
|
- <field name="NUM">64</field>
|
|
|
- </shadow>
|
|
|
- </value>
|
|
|
- <value name="DIVISOR">
|
|
|
- <shadow type="math_number">
|
|
|
- <field name="NUM">10</field>
|
|
|
- </shadow>
|
|
|
- </value>
|
|
|
- </block>
|
|
|
- <block type="math_constrain">
|
|
|
- <value name="VALUE">
|
|
|
- <shadow type="math_number">
|
|
|
- <field name="NUM">50</field>
|
|
|
- </shadow>
|
|
|
- </value>
|
|
|
- <value name="LOW">
|
|
|
- <shadow type="math_number">
|
|
|
- <field name="NUM">1</field>
|
|
|
- </shadow>
|
|
|
- </value>
|
|
|
- <value name="HIGH">
|
|
|
- <shadow type="math_number">
|
|
|
- <field name="NUM">100</field>
|
|
|
- </shadow>
|
|
|
- </value>
|
|
|
- </block>
|
|
|
- <block type="math_random_int">
|
|
|
- <value name="FROM">
|
|
|
- <shadow type="math_number">
|
|
|
- <field name="NUM">1</field>
|
|
|
- </shadow>
|
|
|
- </value>
|
|
|
- <value name="TO">
|
|
|
- <shadow type="math_number">
|
|
|
- <field name="NUM">100</field>
|
|
|
- </shadow>
|
|
|
- </value>
|
|
|
- </block>
|
|
|
- <block type="math_random_float"></block>
|
|
|
- <block type="math_atan2">
|
|
|
- <value name="X">
|
|
|
- <shadow type="math_number">
|
|
|
- <field name="NUM">1</field>
|
|
|
- </shadow>
|
|
|
- </value>
|
|
|
- <value name="Y">
|
|
|
- <shadow type="math_number">
|
|
|
- <field name="NUM">1</field>
|
|
|
- </shadow>
|
|
|
- </value>
|
|
|
- </block>
|
|
|
- </category>
|
|
|
- <category name="文本" categorystyle="text_category">
|
|
|
- <block type="text"></block>
|
|
|
- <block type="text_join"></block>
|
|
|
- <block type="text_append">
|
|
|
- <value name="TEXT">
|
|
|
- <shadow type="text"></shadow>
|
|
|
- </value>
|
|
|
- </block>
|
|
|
- <block type="text_length">
|
|
|
- <value name="VALUE">
|
|
|
- <shadow type="text">
|
|
|
- <field name="TEXT">abc</field>
|
|
|
- </shadow>
|
|
|
- </value>
|
|
|
- </block>
|
|
|
- <block type="text_isEmpty">
|
|
|
- <value name="VALUE">
|
|
|
- <shadow type="text">
|
|
|
- <field name="TEXT"></field>
|
|
|
- </shadow>
|
|
|
- </value>
|
|
|
- </block>
|
|
|
- <block type="text_indexOf">
|
|
|
- <value name="VALUE">
|
|
|
- <block type="variables_get">
|
|
|
- <field name="VAR">text</field>
|
|
|
- </block>
|
|
|
- </value>
|
|
|
- <value name="FIND">
|
|
|
- <shadow type="text">
|
|
|
- <field name="TEXT">abc</field>
|
|
|
- </shadow>
|
|
|
- </value>
|
|
|
+ <div class="box-blockly">
|
|
|
+ <div id="blocklyDiv" style="height: 100%; width: 100%; background-color: #cccccc"></div>
|
|
|
+
|
|
|
+ <!-- blockly工具栏 -->
|
|
|
+ <template>
|
|
|
+ <xml 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>
|
|
|
+ </category>
|
|
|
+
|
|
|
+ <!-- 其他原有分类保持不变 -->
|
|
|
+ <category name="逻辑" categorystyle="logic_category">
|
|
|
+ <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>
|
|
|
+ <block type="logic_null" disabled="true"></block>
|
|
|
+ <block type="logic_ternary"></block>
|
|
|
+ </category>
|
|
|
+ <category name="循环" categorystyle="loop_category">
|
|
|
+ <block type="controls_repeat_ext">
|
|
|
+ <value name="TIMES">
|
|
|
+ <shadow type="math_number">
|
|
|
+ <field name="NUM">10</field>
|
|
|
+ </shadow>
|
|
|
+ </value>
|
|
|
+ </block>
|
|
|
+ <block type="controls_repeat" disabled="true"></block>
|
|
|
+ <block type="controls_whileUntil"></block>
|
|
|
+ <block type="controls_for">
|
|
|
+ <value name="FROM">
|
|
|
+ <shadow type="math_number">
|
|
|
+ <field name="NUM">1</field>
|
|
|
+ </shadow>
|
|
|
+ </value>
|
|
|
+ <value name="TO">
|
|
|
+ <shadow type="math_number">
|
|
|
+ <field name="NUM">10</field>
|
|
|
+ </shadow>
|
|
|
+ </value>
|
|
|
+ <value name="BY">
|
|
|
+ <shadow type="math_number">
|
|
|
+ <field name="NUM">1</field>
|
|
|
+ </shadow>
|
|
|
+ </value>
|
|
|
+ </block>
|
|
|
+ <block type="controls_forEach"></block>
|
|
|
+ <block type="controls_flow_statements"></block>
|
|
|
+ </category>
|
|
|
+ <category name="数值" categorystyle="math_category">
|
|
|
+ <block type="math_number" gap="32">
|
|
|
+ <field name="NUM">123</field>
|
|
|
+ </block>
|
|
|
+ <block type="math_arithmetic">
|
|
|
+ <value name="A">
|
|
|
+ <shadow type="math_number">
|
|
|
+ <field name="NUM">1</field>
|
|
|
+ </shadow>
|
|
|
+ </value>
|
|
|
+ <value name="B">
|
|
|
+ <shadow type="math_number">
|
|
|
+ <field name="NUM">1</field>
|
|
|
+ </shadow>
|
|
|
+ </value>
|
|
|
+ </block>
|
|
|
+ <block type="math_single">
|
|
|
+ <value name="NUM">
|
|
|
+ <shadow type="math_number">
|
|
|
+ <field name="NUM">9</field>
|
|
|
+ </shadow>
|
|
|
+ </value>
|
|
|
+ </block>
|
|
|
+ <block type="math_trig">
|
|
|
+ <value name="NUM">
|
|
|
+ <shadow type="math_number">
|
|
|
+ <field name="NUM">45</field>
|
|
|
+ </shadow>
|
|
|
+ </value>
|
|
|
+ </block>
|
|
|
+ <block type="math_constant"></block>
|
|
|
+ <block type="math_number_property">
|
|
|
+ <value name="NUMBER_TO_CHECK">
|
|
|
+ <shadow type="math_number">
|
|
|
+ <field name="NUM">0</field>
|
|
|
+ </shadow>
|
|
|
+ </value>
|
|
|
+ </block>
|
|
|
+ <block type="math_round">
|
|
|
+ <value name="NUM">
|
|
|
+ <shadow type="math_number">
|
|
|
+ <field name="NUM">3.1</field>
|
|
|
+ </shadow>
|
|
|
+ </value>
|
|
|
+ </block>
|
|
|
+ <block type="math_on_list"></block>
|
|
|
+ <block type="math_modulo">
|
|
|
+ <value name="DIVIDEND">
|
|
|
+ <shadow type="math_number">
|
|
|
+ <field name="NUM">64</field>
|
|
|
+ </shadow>
|
|
|
+ </value>
|
|
|
+ <value name="DIVISOR">
|
|
|
+ <shadow type="math_number">
|
|
|
+ <field name="NUM">10</field>
|
|
|
+ </shadow>
|
|
|
+ </value>
|
|
|
+ </block>
|
|
|
+ <block type="math_constrain">
|
|
|
+ <value name="VALUE">
|
|
|
+ <shadow type="math_number">
|
|
|
+ <field name="NUM">50</field>
|
|
|
+ </shadow>
|
|
|
+ </value>
|
|
|
+ <value name="LOW">
|
|
|
+ <shadow type="math_number">
|
|
|
+ <field name="NUM">1</field>
|
|
|
+ </shadow>
|
|
|
+ </value>
|
|
|
+ <value name="HIGH">
|
|
|
+ <shadow type="math_number">
|
|
|
+ <field name="NUM">100</field>
|
|
|
+ </shadow>
|
|
|
+ </value>
|
|
|
+ </block>
|
|
|
+ <block type="math_random_int">
|
|
|
+ <value name="FROM">
|
|
|
+ <shadow type="math_number">
|
|
|
+ <field name="NUM">1</field>
|
|
|
+ </shadow>
|
|
|
+ </value>
|
|
|
+ <value name="TO">
|
|
|
+ <shadow type="math_number">
|
|
|
+ <field name="NUM">100</field>
|
|
|
+ </shadow>
|
|
|
+ </value>
|
|
|
+ </block>
|
|
|
+ <block type="math_random_float"></block>
|
|
|
+ <block type="math_atan2">
|
|
|
+ <value name="X">
|
|
|
+ <shadow type="math_number">
|
|
|
+ <field name="NUM">1</field>
|
|
|
+ </shadow>
|
|
|
+ </value>
|
|
|
+ <value name="Y">
|
|
|
+ <shadow type="math_number">
|
|
|
+ <field name="NUM">1</field>
|
|
|
+ </shadow>
|
|
|
+ </value>
|
|
|
+ </block>
|
|
|
+ </category>
|
|
|
+ <category name="文本" categorystyle="text_category">
|
|
|
+ <block type="text"></block>
|
|
|
+ <block type="text_join"></block>
|
|
|
+ <block type="text_append">
|
|
|
+ <value name="TEXT">
|
|
|
+ <shadow type="text"></shadow>
|
|
|
+ </value>
|
|
|
+ </block>
|
|
|
+ <block type="text_length">
|
|
|
+ <value name="VALUE">
|
|
|
+ <shadow type="text">
|
|
|
+ <field name="TEXT">abc</field>
|
|
|
+ </shadow>
|
|
|
+ </value>
|
|
|
+ </block>
|
|
|
+ <block type="text_isEmpty">
|
|
|
+ <value name="VALUE">
|
|
|
+ <shadow type="text">
|
|
|
+ <field name="TEXT"></field>
|
|
|
+ </shadow>
|
|
|
+ </value>
|
|
|
+ </block>
|
|
|
+ <block type="text_indexOf">
|
|
|
+ <value name="VALUE">
|
|
|
+ <block type="variables_get">
|
|
|
+ <field name="VAR">text</field>
|
|
|
</block>
|
|
|
- <block type="text_charAt">
|
|
|
- <value name="VALUE">
|
|
|
- <block type="variables_get">
|
|
|
- <field name="VAR">text</field>
|
|
|
- </block>
|
|
|
- </value>
|
|
|
+ </value>
|
|
|
+ <value name="FIND">
|
|
|
+ <shadow type="text">
|
|
|
+ <field name="TEXT">abc</field>
|
|
|
+ </shadow>
|
|
|
+ </value>
|
|
|
+ </block>
|
|
|
+ <block type="text_charAt">
|
|
|
+ <value name="VALUE">
|
|
|
+ <block type="variables_get">
|
|
|
+ <field name="VAR">text</field>
|
|
|
</block>
|
|
|
- <block type="text_getSubstring">
|
|
|
- <value name="STRING">
|
|
|
- <block type="variables_get">
|
|
|
- <field name="VAR">text</field>
|
|
|
- </block>
|
|
|
- </value>
|
|
|
+ </value>
|
|
|
+ </block>
|
|
|
+ <block type="text_getSubstring">
|
|
|
+ <value name="STRING">
|
|
|
+ <block type="variables_get">
|
|
|
+ <field name="VAR">text</field>
|
|
|
</block>
|
|
|
- <block type="text_changeCase">
|
|
|
- <value name="TEXT">
|
|
|
- <shadow type="text">
|
|
|
- <field name="TEXT">abc</field>
|
|
|
- </shadow>
|
|
|
- </value>
|
|
|
+ </value>
|
|
|
+ </block>
|
|
|
+ <block type="text_changeCase">
|
|
|
+ <value name="TEXT">
|
|
|
+ <shadow type="text">
|
|
|
+ <field name="TEXT">abc</field>
|
|
|
+ </shadow>
|
|
|
+ </value>
|
|
|
+ </block>
|
|
|
+ <block type="text_trim">
|
|
|
+ <value name="TEXT">
|
|
|
+ <shadow type="text">
|
|
|
+ <field name="TEXT">abc</field>
|
|
|
+ </shadow>
|
|
|
+ </value>
|
|
|
+ </block>
|
|
|
+ <block type="text_count">
|
|
|
+ <value name="SUB">
|
|
|
+ <shadow type="text"></shadow>
|
|
|
+ </value>
|
|
|
+ <value name="TEXT">
|
|
|
+ <shadow type="text"></shadow>
|
|
|
+ </value>
|
|
|
+ </block>
|
|
|
+ <block type="text_replace">
|
|
|
+ <value name="FROM">
|
|
|
+ <shadow type="text"></shadow>
|
|
|
+ </value>
|
|
|
+ <value name="TO">
|
|
|
+ <shadow type="text"></shadow>
|
|
|
+ </value>
|
|
|
+ <value name="TEXT">
|
|
|
+ <shadow type="text"></shadow>
|
|
|
+ </value>
|
|
|
+ </block>
|
|
|
+ <block type="text_reverse">
|
|
|
+ <value name="TEXT">
|
|
|
+ <shadow type="text"></shadow>
|
|
|
+ </value>
|
|
|
+ </block>
|
|
|
+ <label text="Input/Output:" web-class="ioLabel"></label>
|
|
|
+ <block type="text_print">
|
|
|
+ <value name="TEXT">
|
|
|
+ <shadow type="text">
|
|
|
+ <field name="TEXT">abc</field>
|
|
|
+ </shadow>
|
|
|
+ </value>
|
|
|
+ </block>
|
|
|
+ <block type="text_prompt_ext">
|
|
|
+ <value name="TEXT">
|
|
|
+ <shadow type="text">
|
|
|
+ <field name="TEXT">abc</field>
|
|
|
+ </shadow>
|
|
|
+ </value>
|
|
|
+ </block>
|
|
|
+ </category>
|
|
|
+ <category name="列表" categorystyle="list_category">
|
|
|
+ <block type="lists_create_with">
|
|
|
+ <mutation items="0"></mutation>
|
|
|
+ </block>
|
|
|
+ <block type="lists_create_with"></block>
|
|
|
+ <block type="lists_repeat">
|
|
|
+ <value name="NUM">
|
|
|
+ <shadow type="math_number">
|
|
|
+ <field name="NUM">5</field>
|
|
|
+ </shadow>
|
|
|
+ </value>
|
|
|
+ </block>
|
|
|
+ <block type="lists_length"></block>
|
|
|
+ <block type="lists_isEmpty"></block>
|
|
|
+ <block type="lists_indexOf">
|
|
|
+ <value name="VALUE">
|
|
|
+ <block type="variables_get">
|
|
|
+ <field name="VAR">list</field>
|
|
|
</block>
|
|
|
- <block type="text_trim">
|
|
|
- <value name="TEXT">
|
|
|
- <shadow type="text">
|
|
|
- <field name="TEXT">abc</field>
|
|
|
- </shadow>
|
|
|
- </value>
|
|
|
+ </value>
|
|
|
+ </block>
|
|
|
+ <block type="lists_getIndex">
|
|
|
+ <value name="VALUE">
|
|
|
+ <block type="variables_get">
|
|
|
+ <field name="VAR">list</field>
|
|
|
</block>
|
|
|
- <block type="text_count">
|
|
|
- <value name="SUB">
|
|
|
- <shadow type="text"></shadow>
|
|
|
- </value>
|
|
|
- <value name="TEXT">
|
|
|
- <shadow type="text"></shadow>
|
|
|
- </value>
|
|
|
+ </value>
|
|
|
+ </block>
|
|
|
+ <block type="lists_setIndex">
|
|
|
+ <value name="LIST">
|
|
|
+ <block type="variables_get">
|
|
|
+ <field name="VAR">list</field>
|
|
|
</block>
|
|
|
- <block type="text_replace">
|
|
|
- <value name="FROM">
|
|
|
- <shadow type="text"></shadow>
|
|
|
- </value>
|
|
|
- <value name="TO">
|
|
|
- <shadow type="text"></shadow>
|
|
|
- </value>
|
|
|
- <value name="TEXT">
|
|
|
- <shadow type="text"></shadow>
|
|
|
- </value>
|
|
|
+ </value>
|
|
|
+ </block>
|
|
|
+ <block type="lists_getSublist">
|
|
|
+ <value name="LIST">
|
|
|
+ <block type="variables_get">
|
|
|
+ <field name="VAR">list</field>
|
|
|
</block>
|
|
|
- <block type="text_reverse">
|
|
|
- <value name="TEXT">
|
|
|
- <shadow type="text"></shadow>
|
|
|
- </value>
|
|
|
- </block>
|
|
|
- <label text="Input/Output:" web-class="ioLabel"></label>
|
|
|
- <block type="text_print">
|
|
|
- <value name="TEXT">
|
|
|
- <shadow type="text">
|
|
|
- <field name="TEXT">abc</field>
|
|
|
- </shadow>
|
|
|
- </value>
|
|
|
- </block>
|
|
|
- <block type="text_prompt_ext">
|
|
|
- <value name="TEXT">
|
|
|
- <shadow type="text">
|
|
|
- <field name="TEXT">abc</field>
|
|
|
- </shadow>
|
|
|
- </value>
|
|
|
- </block>
|
|
|
- </category>
|
|
|
- <category name="列表" categorystyle="list_category">
|
|
|
- <block type="lists_create_with">
|
|
|
- <mutation items="0"></mutation>
|
|
|
- </block>
|
|
|
- <block type="lists_create_with"></block>
|
|
|
- <block type="lists_repeat">
|
|
|
- <value name="NUM">
|
|
|
- <shadow type="math_number">
|
|
|
- <field name="NUM">5</field>
|
|
|
- </shadow>
|
|
|
- </value>
|
|
|
- </block>
|
|
|
- <block type="lists_length"></block>
|
|
|
- <block type="lists_isEmpty"></block>
|
|
|
- <block type="lists_indexOf">
|
|
|
- <value name="VALUE">
|
|
|
- <block type="variables_get">
|
|
|
- <field name="VAR">list</field>
|
|
|
- </block>
|
|
|
- </value>
|
|
|
- </block>
|
|
|
- <block type="lists_getIndex">
|
|
|
- <value name="VALUE">
|
|
|
- <block type="variables_get">
|
|
|
- <field name="VAR">list</field>
|
|
|
- </block>
|
|
|
- </value>
|
|
|
- </block>
|
|
|
- <block type="lists_setIndex">
|
|
|
- <value name="LIST">
|
|
|
- <block type="variables_get">
|
|
|
- <field name="VAR">list</field>
|
|
|
- </block>
|
|
|
- </value>
|
|
|
- </block>
|
|
|
- <block type="lists_getSublist">
|
|
|
- <value name="LIST">
|
|
|
- <block type="variables_get">
|
|
|
- <field name="VAR">list</field>
|
|
|
- </block>
|
|
|
- </value>
|
|
|
- </block>
|
|
|
- <block type="lists_split">
|
|
|
- <value name="DELIM">
|
|
|
- <shadow type="text">
|
|
|
- <field name="TEXT">,</field>
|
|
|
- </shadow>
|
|
|
- </value>
|
|
|
- </block>
|
|
|
- <block type="lists_sort"></block>
|
|
|
- <block type="lists_reverse"></block>
|
|
|
- </category>
|
|
|
- <sep></sep>
|
|
|
- <category
|
|
|
- name="Variables"
|
|
|
- categorystyle="variable_category"
|
|
|
- custom="VARIABLE"></category>
|
|
|
- <category
|
|
|
- name="Functions"
|
|
|
- categorystyle="procedure_category"
|
|
|
- custom="PROCEDURE"></category>
|
|
|
- </xml>
|
|
|
- </template>
|
|
|
- </div>
|
|
|
+ </value>
|
|
|
+ </block>
|
|
|
+ <block type="lists_split">
|
|
|
+ <value name="DELIM">
|
|
|
+ <shadow type="text">
|
|
|
+ <field name="TEXT">,</field>
|
|
|
+ </shadow>
|
|
|
+ </value>
|
|
|
+ </block>
|
|
|
+ <block type="lists_sort"></block>
|
|
|
+ <block type="lists_reverse"></block>
|
|
|
+ </category>
|
|
|
+ <sep></sep>
|
|
|
+ <category
|
|
|
+ name="Variables"
|
|
|
+ categorystyle="variable_category"
|
|
|
+ custom="VARIABLE"></category>
|
|
|
+ <category
|
|
|
+ name="Functions"
|
|
|
+ categorystyle="procedure_category"
|
|
|
+ custom="PROCEDURE"></category>
|
|
|
+ </xml>
|
|
|
+ </template>
|
|
|
+ </div>
|
|
|
|
|
|
- <div class="box-code">
|
|
|
- <el-button type="info" plain id="save-json" @click="saveJson()">保存 JSON</el-button>
|
|
|
- <el-button type="info" plain id="save-xml" @click="saveXml()">保存 XML</el-button>
|
|
|
- <el-button type="warning" plain id="import" @click="load()">重新加载</el-button>
|
|
|
- <br/><br/>
|
|
|
-
|
|
|
- <!-- 添加生成 JavaScript 和 Python 代码的按钮 -->
|
|
|
- <el-button type="primary" plain id="to-code-py" @click="generateCode('javascript')">生成 JavaScript</el-button>
|
|
|
- <el-button type="success" plain id="to-code-py" @click="generateCode('python')">生成 Python</el-button>
|
|
|
- <br/><br/>
|
|
|
- <!-- blockly代码区 -->
|
|
|
- <textarea name="" id="textarea" class="box-code-textarea"></textarea>
|
|
|
+ <div class="box-code">
|
|
|
+ <div class="button-container">
|
|
|
+ <el-button type="danger" plain id="run-code" @click="runCode()">运行代码</el-button>
|
|
|
+ <el-button type="warning" plain id="import" @click="load()">重新加载</el-button>
|
|
|
+ </div>
|
|
|
+ <br/>
|
|
|
+
|
|
|
+ <div class="button-container">
|
|
|
+ <el-button type="primary" plain id="to-code-js" @click="generateCode('javascript')">生成 JavaScript</el-button>
|
|
|
+ <el-button type="success" plain id="to-code-py" @click="generateCode('python')">生成 Python</el-button>
|
|
|
+ <el-button type="info" plain id="save-json" @click="saveJson()">保存 JSON</el-button>
|
|
|
+ <el-button type="info" plain id="save-xml" @click="saveXml()">保存 XML</el-button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 代码显示区 -->
|
|
|
+ <div class="code-section">
|
|
|
+ <h4>生成的代码</h4>
|
|
|
+ <textarea name="" id="textarea" class="box-code-textarea"></textarea>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 运行结果区 -->
|
|
|
+ <div class="result-section">
|
|
|
+ <h4>运行结果</h4>
|
|
|
+ <div id="run-result" class="run-result-content"></div>
|
|
|
+ <!-- 添加额外的图片预览区域 -->
|
|
|
+ <div v-if="state.generatedContent.imageUrl" class="extra-image-preview">
|
|
|
+ <h5>生成的图片:</h5>
|
|
|
+ <img :src="state.generatedContent.imageUrl" class="extra-preview-image" alt="AI生成图片">
|
|
|
</div>
|
|
|
</div>
|
|
|
</div>
|
|
|
+
|
|
|
+ <!-- AI结果预览模态框 -->
|
|
|
+ <el-dialog
|
|
|
+ title="AI生成结果"
|
|
|
+ :visible.sync="state.previewVisible"
|
|
|
+ width="80%"
|
|
|
+ :before-close="handleClosePreview"
|
|
|
+ >
|
|
|
+ <div v-if="state.previewType === 'image'" class="preview-image-container">
|
|
|
+ <img :src="state.previewContent" class="preview-image" alt="生成的图片">
|
|
|
+ </div>
|
|
|
+ <div v-if="state.previewType === 'video'" class="preview-video-container">
|
|
|
+ <video :src="state.previewContent" controls class="preview-video"></video>
|
|
|
+ </div>
|
|
|
+ <div v-if="state.previewType === 'text'" class="preview-text-container">
|
|
|
+ <p>{{ state.previewContent }}</p>
|
|
|
+ </div>
|
|
|
+ <span slot="footer" class="dialog-footer">
|
|
|
+ <el-button @click="handleClosePreview">关闭</el-button>
|
|
|
+ </span>
|
|
|
+ </el-dialog>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
<script setup>
|
|
|
-import { ref, onMounted } from 'vue'
|
|
|
-import { useRouter } from 'vue-router'
|
|
|
-import {
|
|
|
- Expand,
|
|
|
- Fold,
|
|
|
- Memo,
|
|
|
- Delete
|
|
|
-} from '@element-plus/icons-vue'
|
|
|
-import { Search, ArrowLeftBold } from '@element-plus/icons-vue'
|
|
|
-import {Message} from "@/utils/message/Message.js";
|
|
|
-import Blockly from 'blockly';
|
|
|
-import {javascriptGenerator} from 'blockly/javascript';
|
|
|
-// 引入 Python 代码生成器
|
|
|
-import {pythonGenerator} from 'blockly/python';
|
|
|
-// 引入想要转换的语言,语言有php python dart lua javascript
|
|
|
-// import 'blockly/javascript'
|
|
|
-// 引入语言包并使用
|
|
|
+import { ref, onMounted, onUnmounted, reactive } from 'vue'
|
|
|
+import * as Blockly from 'blockly';
|
|
|
+import { javascriptGenerator } from 'blockly/javascript';
|
|
|
+import { pythonGenerator } from 'blockly/python';
|
|
|
import * as hans from 'blockly/msg/zh-hans'
|
|
|
-Blockly.setLocale(hans);
|
|
|
+import { ElDialog, ElButton, ElMessage } from 'element-plus';
|
|
|
|
|
|
-const router = useRouter() // 获取当前路由对象
|
|
|
-// 搜索框
|
|
|
-const SearchInput = ref('')
|
|
|
-// 添加抽屉显示状态
|
|
|
-const drawerVisible = ref(true)
|
|
|
-// 添加切换抽屉显示状态的函数
|
|
|
-const toggleDrawer = () => {
|
|
|
- drawerVisible.value = !drawerVisible.value
|
|
|
-}
|
|
|
+//【文生图】文生图
|
|
|
+import {AiImageStatusEnum, CreatePainting, PaintingGetMys} from '@/api/questions.js'
|
|
|
+import { getModelIdByType } from '@/api/teachers.js'
|
|
|
+import { ModelTypeEnum } from '@/api/teachers.js'
|
|
|
+import { globalState } from '@/utils/globalState.js'
|
|
|
|
|
|
-// 返回上一页
|
|
|
-const goBack = () => {
|
|
|
- router.go(-1)
|
|
|
-}
|
|
|
-// 渲染页面标题
|
|
|
-const boxIconTitle = ref('')
|
|
|
-// 使用 ref 存储 workspace
|
|
|
-const workspace = ref(null)
|
|
|
+Blockly.setLocale(hans);
|
|
|
|
|
|
-onMounted(async () => {
|
|
|
- const typeId = router.currentRoute.value.query.typeId
|
|
|
- console.log(typeId,"----")
|
|
|
- if (typeId) {
|
|
|
+// 状态管理
|
|
|
+const state = reactive({
|
|
|
+ workspace: null,
|
|
|
+ generatedContent: {
|
|
|
+ imageUrl: '',
|
|
|
+ videoUrl: '',
|
|
|
+ text: ''
|
|
|
+ },
|
|
|
+ previewVisible: false,
|
|
|
+ previewType: '',
|
|
|
+ previewContent: '',
|
|
|
+ isProcessing: false,
|
|
|
|
|
|
- }
|
|
|
- const title = router.currentRoute.value.query.typeName
|
|
|
- if (title) {
|
|
|
- boxIconTitle.value = String(title)
|
|
|
- }
|
|
|
+//【文生图】文生图
|
|
|
+ modelId: 0,
|
|
|
+ gradeId: '',
|
|
|
+ inProgressImageMap: {}
|
|
|
+});
|
|
|
|
|
|
- workspace.value = Blockly.inject('blocklyDiv',
|
|
|
- {
|
|
|
- //工具栏
|
|
|
- toolbox: document.getElementById('toolbox'),
|
|
|
- //网格效果
|
|
|
- grid:{spacing: 20,length: 3,colour: '#ccc',snap: true}
|
|
|
- }
|
|
|
- );
|
|
|
- // 工作区监听代码生成器
|
|
|
- // 修改为传递一个函数
|
|
|
- workspace.value.addChangeListener(() => generateCode("python"));
|
|
|
-})
|
|
|
+// 【文生图】自动刷新image列表的定时器
|
|
|
+const inProgressTimer = ref();
|
|
|
|
|
|
-//保存Json
|
|
|
-function saveJson() {
|
|
|
- var output = document.getElementById('textarea');
|
|
|
- var state = Blockly.serialization.workspaces.save(workspace.value);
|
|
|
- output.value = JSON.stringify(state, null, 2);
|
|
|
- output.focus();
|
|
|
- output.select();
|
|
|
- taChange();
|
|
|
-}
|
|
|
-
|
|
|
-//保存Xml
|
|
|
-function saveXml() {
|
|
|
- var output = document.getElementById('textarea');
|
|
|
- var xml = Blockly.Xml.workspaceToDom(workspace.value);
|
|
|
- output.value = Blockly.Xml.domToPrettyText(xml);
|
|
|
- output.focus();
|
|
|
- output.select();
|
|
|
- taChange();
|
|
|
-}
|
|
|
-
|
|
|
-//重新加载
|
|
|
-function load() {
|
|
|
- var input = document.getElementById('textarea');
|
|
|
- if (!input.value) {
|
|
|
- return;
|
|
|
- }
|
|
|
- var valid = saveIsValid(input.value);
|
|
|
- if (valid.json) {
|
|
|
- var state = JSON.parse(input.value);
|
|
|
- Blockly.serialization.workspaces.load(state, workspace.value);
|
|
|
- } else if (valid.xml) {
|
|
|
- var xml = Blockly.utils.xml.textToDom(input.value);
|
|
|
- Blockly.Xml.domToWorkspace(xml, workspace.value);
|
|
|
+// 初始化Blockly工作区和自定义积木
|
|
|
+onMounted(async () => {
|
|
|
+ // 从全局状态初始化年级ID
|
|
|
+ state.gradeId = globalState.initGradeId()
|
|
|
+ try{
|
|
|
+ // 获取modelId
|
|
|
+ const modelRes = await getModelIdByType({ type: ModelTypeEnum.TEXT_TO_IMAGE, platform: "DouBao" })
|
|
|
+ state.modelId = modelRes.data
|
|
|
+ }catch(error){
|
|
|
+ console.error('获取modelId失败:', error);
|
|
|
}
|
|
|
- taChange();
|
|
|
-}
|
|
|
|
|
|
-//
|
|
|
-function taChange() {
|
|
|
- var textarea = document.getElementById('textarea');
|
|
|
- if (sessionStorage) {
|
|
|
- sessionStorage.setItem('textarea', textarea.value);
|
|
|
- }
|
|
|
- var valid = saveIsValid(textarea.value);
|
|
|
- document.getElementById('import').disabled = !valid.json && !valid.xml;
|
|
|
-}
|
|
|
+ // 注册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('');
|
|
|
+ }
|
|
|
+ };
|
|
|
|
|
|
-function saveIsValid(save) {
|
|
|
- var validJson = true;
|
|
|
- try {
|
|
|
- JSON.parse(save);
|
|
|
- } catch (e) {
|
|
|
- validJson = false;
|
|
|
- }
|
|
|
- var validXml = true;
|
|
|
- try {
|
|
|
- Blockly.utils.xml.textToDom(save);
|
|
|
- } catch (e) {
|
|
|
- validXml = false;
|
|
|
- }
|
|
|
- return {
|
|
|
- json: validJson,
|
|
|
- xml: validXml,
|
|
|
+ // 注册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('');
|
|
|
+ }
|
|
|
};
|
|
|
-}
|
|
|
|
|
|
-// 定义生成代码的函数
|
|
|
-const generateCode = (language) => {
|
|
|
- if (!workspace.value) {
|
|
|
- console.error('workspace 未正确初始化');
|
|
|
- return;
|
|
|
- }
|
|
|
- let generator;
|
|
|
- if (language === 'javascript') {
|
|
|
- generator = javascriptGenerator;
|
|
|
- } else if (language === 'python') {
|
|
|
- generator = pythonGenerator;
|
|
|
- } else {
|
|
|
- console.error('不支持的语言类型');
|
|
|
- return;
|
|
|
- }
|
|
|
- const code = generator.workspaceToCode(workspace.value);
|
|
|
- document.getElementById('textarea').value = code;
|
|
|
-};
|
|
|
-// 代码生成器
|
|
|
-const myUpdateFunction = () => {
|
|
|
- // 修改此处,传入 workspace.value
|
|
|
- const code = javascriptGenerator.workspaceToCode(workspace.value);
|
|
|
- document.getElementById('textarea').value = code;
|
|
|
-};
|
|
|
+ // 注册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('');
|
|
|
+ }
|
|
|
+ };
|
|
|
|
|
|
-// 获取 blockly 工作区中的 code 和 xml 结构
|
|
|
-const getBlockData = () => {
|
|
|
- if (workspace.value) {
|
|
|
- const code = Blockly.JavaScript.workspaceToCode(workspace.value);
|
|
|
- const xml = Blockly.Xml.workspaceToDom(workspace.value)
|
|
|
- const xmlText = Blockly.Xml.domToText(xml);
|
|
|
- // 可以在这里返回或处理 code 和 xmlText
|
|
|
- return { code, xmlText };
|
|
|
- } else {
|
|
|
- console.error('workspace 或 Blockly.JavaScript 未正确初始化');
|
|
|
- }
|
|
|
-};
|
|
|
+ // 注册AI文本生成文本积木
|
|
|
+ Blockly.Blocks['ai_text_to_text'] = {
|
|
|
+ init: function() {
|
|
|
+ this.appendDummyInput()
|
|
|
+ .appendField('AI文本处理');
|
|
|
+ this.appendValueInput('PROMPT')
|
|
|
+ .setCheck('String')
|
|
|
+ .appendField('输入文本:');
|
|
|
+ this.appendDummyInput()
|
|
|
+ .appendField('模型:')
|
|
|
+ .appendField(new Blockly.FieldDropdown([
|
|
|
+ ['默认', 'default'],
|
|
|
+ ['对话', 'chat'],
|
|
|
+ ['分析', 'analysis']
|
|
|
+ ]), 'MODEL');
|
|
|
+ this.setOutput(true, 'String');
|
|
|
+ this.setColour(300);
|
|
|
+ this.setTooltip('使用AI处理文本并返回结果');
|
|
|
+ this.setHelpUrl('');
|
|
|
+ }
|
|
|
+ };
|
|
|
|
|
|
-// 回显工作区中的 xml 结构
|
|
|
-const setBlockData = (xmlText) => {
|
|
|
- if (workspace.value) {
|
|
|
- clearBlockData();
|
|
|
- const xml = Blockly.Xml.textToDom(xmlText);
|
|
|
- Blockly.Xml.domToWorkspace(xml, workspace.value);
|
|
|
- } else {
|
|
|
- console.error('workspace 未正确初始化');
|
|
|
+ // 注册JavaScript代码生成器
|
|
|
+ registerJavaScriptGenerators();
|
|
|
+
|
|
|
+ // 注册Python代码生成器
|
|
|
+ registerPythonGenerators();
|
|
|
+
|
|
|
+ // 初始化工作区
|
|
|
+ state.workspace = Blockly.inject('blocklyDiv', {
|
|
|
+ toolbox: document.getElementById('toolbox'),
|
|
|
+ grid: { spacing: 20, length: 3, colour: '#ccc', snap: true },
|
|
|
+ trashcan: true
|
|
|
+ });
|
|
|
+
|
|
|
+ // 工作区变化时自动生成JavaScript代码
|
|
|
+ state.workspace.addChangeListener(() => generateCode("javascript"));
|
|
|
+});
|
|
|
+// 组件卸载时清除定时器
|
|
|
+onUnmounted(() => {
|
|
|
+ if (inProgressTimer.value) {
|
|
|
+ clearInterval(inProgressTimer.value);
|
|
|
}
|
|
|
-};
|
|
|
+});
|
|
|
|
|
|
-// 清空工作区
|
|
|
-const clearBlockData = () => {
|
|
|
- if (workspace.value) {
|
|
|
- workspace.value.clear();
|
|
|
- } else {
|
|
|
- console.error('workspace 未正确初始化');
|
|
|
+
|
|
|
+// AI服务配置
|
|
|
+const aiServiceConfig = {
|
|
|
+ baseUrl: import.meta.env.VITE_BASE_URL + '/bjdxWeb/ai',
|
|
|
+ endpoints: {
|
|
|
+ voiceRecognition: '/voice-recognition',
|
|
|
+ textToImage: '/create-painting',
|
|
|
+ textToVideo: '/text-to-video',
|
|
|
+ textToText: '/text-to-text'
|
|
|
}
|
|
|
};
|
|
|
|
|
|
-
|
|
|
-// 菜单打开和关闭的处理函数
|
|
|
-const handleOpen = () => {}
|
|
|
-const handleClose = () => {}
|
|
|
-
|
|
|
-// 动态渲染树型结构
|
|
|
-const menuItems = ref([
|
|
|
- { index: '1-1', title: '课前回顾' },
|
|
|
- { index: '1-2', title: '课程引入' },
|
|
|
- {
|
|
|
- index: '1',
|
|
|
- title: '知识讲解',
|
|
|
- children: [
|
|
|
- { index: '1-3', title: '图灵测试' },
|
|
|
- { index: '1-4', title: '课堂提问' },
|
|
|
- { index: '1-5', title: '达特茅斯会议' },
|
|
|
- { index: '1-6', title: '课堂选择' },
|
|
|
- { index: '1-7', title: '聊天机器人' },
|
|
|
- { index: '1-8', title: '专家系统' },
|
|
|
- { index: '1-9', title: '课堂互动' },
|
|
|
- { index: '1-10', title: '互联网快速发展' },
|
|
|
- { index: '1-11', title: '黄金时代' },
|
|
|
- { index: '1-12', title: '课堂提问' },
|
|
|
- { index: '1-13', title: '大模型时代' }
|
|
|
- ]
|
|
|
- },
|
|
|
- { index: '1-14', title: '趣味实操' },
|
|
|
- { index: '1-15', title: '课程总结' }
|
|
|
-])
|
|
|
-
|
|
|
-// 当前播放的索引
|
|
|
-const currentIndex = ref('1-1')
|
|
|
-
|
|
|
-// 渲染课程标题到视频上方
|
|
|
-const smallTitle = ref('课前回顾')
|
|
|
-const handleSelect = index => {
|
|
|
- //测试账号禁用视频
|
|
|
- if (disableVideo(index))return;
|
|
|
-
|
|
|
- const findTitle = items => {
|
|
|
- for (const item of items) {
|
|
|
- if (item.index === index) {
|
|
|
- return item.title
|
|
|
- }
|
|
|
- if (item.children) {
|
|
|
- const result = findTitle(item.children)
|
|
|
- if (result) {
|
|
|
- return result
|
|
|
- }
|
|
|
+// AI服务模块 - 统一管理API调用
|
|
|
+const aiService = {
|
|
|
+ // 语音识别
|
|
|
+ async recognizeVoice(promptText = '', language = 'zh-CN') {
|
|
|
+ console.log('recognizeVoice', promptText, language);
|
|
|
+ // 前端语音采集
|
|
|
+ const recognitionResult = await this.captureVoice(language, promptText);
|
|
|
+ if (!recognitionResult) return '';
|
|
|
+
|
|
|
+ // 可选:将语音识别结果发送到后端进行优化处理
|
|
|
+ try {
|
|
|
+ const response = await fetch(`${aiServiceConfig.baseUrl}${aiServiceConfig.endpoints.voiceRecognition}`, {
|
|
|
+ method: 'POST',
|
|
|
+ headers: { 'Content-Type': 'application/json' },
|
|
|
+ body: JSON.stringify({
|
|
|
+ text: recognitionResult,
|
|
|
+ language
|
|
|
+ })
|
|
|
+ });
|
|
|
+
|
|
|
+ if (response.ok) {
|
|
|
+ const data = await response.json();
|
|
|
+ return data.optimizedText || recognitionResult;
|
|
|
}
|
|
|
+ } catch (error) {
|
|
|
+ console.warn('语音识别优化失败,使用原始结果', error);
|
|
|
}
|
|
|
- return null
|
|
|
- }
|
|
|
- const title = findTitle(menuItems.value)
|
|
|
- if (title) {
|
|
|
- smallTitle.value = title
|
|
|
- }
|
|
|
- // 根据索引切换视频
|
|
|
- if (videoMap[index]) {
|
|
|
- videoSrc.value = videoMap[index]
|
|
|
- currentIndex.value = index
|
|
|
- }
|
|
|
|
|
|
-}
|
|
|
+ return recognitionResult;
|
|
|
+ },
|
|
|
|
|
|
-// 展平所有菜单项索引
|
|
|
-const flattenMenuItems = () => {
|
|
|
- const indices = []
|
|
|
- const traverse = items => {
|
|
|
- for (const item of items) {
|
|
|
- if (!item.children) {
|
|
|
- indices.push(item.index)
|
|
|
- } else {
|
|
|
- traverse(item.children)
|
|
|
+ // 前端语音采集
|
|
|
+ captureVoice(language, promptText) {
|
|
|
+ console.log('captureVoice', language, promptText);
|
|
|
+ return new Promise((resolve) => {
|
|
|
+ if (!('webkitSpeechRecognition' in window) && !('SpeechRecognition' in window)) {
|
|
|
+ ElMessage.warning('您的浏览器不支持语音识别功能');
|
|
|
+ resolve('');
|
|
|
+ return;
|
|
|
}
|
|
|
- }
|
|
|
- }
|
|
|
- traverse(menuItems.value)
|
|
|
- return indices
|
|
|
-}
|
|
|
|
|
|
-//禁用视频
|
|
|
-const disableVideo = (index = currentIndex.value) => {
|
|
|
- let dis = ["1-6","1-7","1-8","1-9","1-10","1-11","1-12","1-13","1-14","1-15"]
|
|
|
+ const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
|
|
|
+ const recognition = new SpeechRecognition();
|
|
|
|
|
|
- if (localStorage.getItem('userName') === "aiTest" &&
|
|
|
- dis.indexOf(index) !== -1) {
|
|
|
+ recognition.lang = language;
|
|
|
+ recognition.interimResults = false;
|
|
|
+ recognition.maxAlternatives = 1;
|
|
|
|
|
|
- if (videoRef.value) {
|
|
|
- // 记录当前播放时间
|
|
|
- videoRef.value.pause()
|
|
|
- // 阻止用户跳转到新的时间点,将播放时间重置为之前的时间
|
|
|
- videoRef.value.currentTime = 0;
|
|
|
- }
|
|
|
- //提示禁用// 显示消息框
|
|
|
- Message().notifyWarning('您的账号并未开放此课程!', true);
|
|
|
- return true;
|
|
|
- }
|
|
|
- return false;
|
|
|
-}
|
|
|
-</script>
|
|
|
+ if (promptText) {
|
|
|
+ ElMessage.info(promptText + '\n请开始说话...');
|
|
|
+ } else {
|
|
|
+ ElMessage.info('请开始说话...');
|
|
|
+ }
|
|
|
|
|
|
-<style scoped lang="scss">
|
|
|
-@use 'sass:math';
|
|
|
-@use 'sass:color'; // 引入 color 模块
|
|
|
-// 定义rpx转换函数
|
|
|
-@function rpx($px) {
|
|
|
- @return math.div($px, 750) * 100vw;
|
|
|
-}
|
|
|
+ recognition.onresult = (event) => {
|
|
|
+ const speechResult = event.results[0][0].transcript;
|
|
|
+ console.log('语音识别结果:', speechResult);
|
|
|
+ resolve(speechResult);
|
|
|
+ };
|
|
|
|
|
|
-// 定义儿童风格的蓝紫色调
|
|
|
-$primary-color: rgba(106, 90, 205, 0.52); // 主色调:蓝紫色
|
|
|
-$secondary-color: rgba(147, 112, 219, 0.66); // 辅助色:亮蓝紫色
|
|
|
-$accent-color: rgb(133, 89, 220); // 强调色:暗蓝紫色
|
|
|
-$light-color: #e6e6fa; // 浅色背景:淡紫色
|
|
|
-$text-color: #483d8b; // 文本颜色:靛蓝色
|
|
|
-
|
|
|
-/* 添加过渡样式 */
|
|
|
-.drawer-slide-enter-active,
|
|
|
-.drawer-slide-leave-active {
|
|
|
- transition: all 0.3s ease;
|
|
|
-}
|
|
|
-.drawer-slide-enter-from,
|
|
|
-.drawer-slide-leave-to {
|
|
|
- transform: translateX(-100%);
|
|
|
- opacity: 0;
|
|
|
-}
|
|
|
-.main-content {
|
|
|
- width: rpx(135);
|
|
|
- height: 100%;
|
|
|
- position: relative;
|
|
|
- background: linear-gradient(to bottom, #001169, #8a78d0);
|
|
|
+ recognition.onerror = (event) => {
|
|
|
+ console.error('语音识别错误:', event.error);
|
|
|
+ ElMessage.error('语音识别发生错误: ' + event.error);
|
|
|
+ resolve('');
|
|
|
+ };
|
|
|
|
|
|
-}
|
|
|
-.content-box {
|
|
|
- flex: 1;
|
|
|
- height: 100%;
|
|
|
- display: flex;
|
|
|
- flex-direction: column; /* 子元素上下排列 */
|
|
|
- background: linear-gradient(
|
|
|
- to bottom,
|
|
|
- #e2ddfc,
|
|
|
- #f1effd
|
|
|
- ); /* 设置悬停、聚焦、点击状态下的背景色 */
|
|
|
-}
|
|
|
-.icon-expand {
|
|
|
- width: rpx(30);
|
|
|
- height: rpx(30);
|
|
|
- z-index: 9999;
|
|
|
- position: absolute;
|
|
|
- cursor: pointer; // 添加鼠标指针样式
|
|
|
-}
|
|
|
-.icon-expand .el-icon {
|
|
|
- font-size: rpx(15);
|
|
|
- position: absolute;
|
|
|
- color: white;
|
|
|
- left: rpx(9);
|
|
|
- margin-top: rpx(17);
|
|
|
-}
|
|
|
-.icon-expand .el-icon:active {
|
|
|
- color: black;
|
|
|
-}
|
|
|
-.home-container {
|
|
|
- position: fixed;
|
|
|
- top: 0;
|
|
|
- left: 0;
|
|
|
- right: 0;
|
|
|
- bottom: 0;
|
|
|
- background: linear-gradient(to bottom, #001169, #b4a8e1);
|
|
|
- display: flex;
|
|
|
- flex-direction: row;
|
|
|
- gap: rpx(0);
|
|
|
-}
|
|
|
-.el-row {
|
|
|
- margin: auto;
|
|
|
- margin-top: rpx(40);
|
|
|
-}
|
|
|
-.tac ::v-deep(.el-menu) {
|
|
|
- background-color: transparent;
|
|
|
- border: none;
|
|
|
- width: 100%;
|
|
|
-}
|
|
|
+ recognition.onend = () => {
|
|
|
+ console.log('语音识别已结束');
|
|
|
+ };
|
|
|
|
|
|
-/* 取消点击后的蓝色字体 el-sub-menu__title*/
|
|
|
-.tac ::v-deep(.el-menu-item.is-active),
|
|
|
-.tac ::v-deep(.el-sub-menu__title.is-active) {
|
|
|
- color: white;
|
|
|
-}
|
|
|
-// 取消鼠标悬浮颜色
|
|
|
-.tac ::v-deep(.el-sub-menu__title) {
|
|
|
- width: rpx(130);
|
|
|
- height: rpx(20);
|
|
|
- margin-bottom: rpx(5);
|
|
|
- border-radius: rpx(6);
|
|
|
-}
|
|
|
-.el-menu ::v-deep(.el-sub-menu__title:hover),
|
|
|
-.el-menu ::v-deep(.el-sub-menu__title:focus),
|
|
|
-.el-menu ::v-deep(.el-sub-menu__title:active) {
|
|
|
- background: linear-gradient(
|
|
|
- to bottom,
|
|
|
- #fee78a,
|
|
|
- #ffce1b
|
|
|
- );
|
|
|
- background-color: transparent;
|
|
|
-}
|
|
|
-.mb-2 {
|
|
|
- color: white;
|
|
|
- margin-top: rpx(1);
|
|
|
-}
|
|
|
-.mb-2 .el-icon {
|
|
|
- font-size: rpx(10);
|
|
|
- margin-left: rpx(5);
|
|
|
-}
|
|
|
-.el-menu-item {
|
|
|
- width: rpx(115);
|
|
|
- height: rpx(20);
|
|
|
- margin-bottom: rpx(5);
|
|
|
- border-radius: rpx(6);
|
|
|
- color: white;
|
|
|
- font-size: rpx(7);
|
|
|
+ recognition.start();
|
|
|
+ });
|
|
|
+ },
|
|
|
|
|
|
-}
|
|
|
-.el-menu ::v-deep(.el-sub-menu__title){
|
|
|
- color: white;
|
|
|
- width: rpx(115);
|
|
|
- height: rpx(20);
|
|
|
- margin-bottom: rpx(5);
|
|
|
- font-size: rpx(7);
|
|
|
-}
|
|
|
-.el-menu ::v-deep(.el-sub-menu__title:hover),
|
|
|
-.el-menu ::v-deep(.el-sub-menu__title:focus),
|
|
|
-.el-menu ::v-deep(.el-sub-menu__title:active) {
|
|
|
- background: linear-gradient(
|
|
|
- to bottom,
|
|
|
- #fee78a,
|
|
|
- #ffce1b
|
|
|
- );
|
|
|
- color: black;
|
|
|
- font-size: rpx(8);
|
|
|
-}
|
|
|
-.el-menu ::v-deep(.el-menu-item:hover),
|
|
|
-.el-menu ::v-deep(.el-menu-item:focus),
|
|
|
-.el-menu ::v-deep(.el-menu-item:active) {
|
|
|
- background: linear-gradient(
|
|
|
- to bottom,
|
|
|
- #fee78a,
|
|
|
- #ffce1b
|
|
|
- );
|
|
|
- color: black;
|
|
|
- font-size: rpx(8);
|
|
|
-}
|
|
|
-.drawer-box {
|
|
|
- position: absolute;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- height: 100%;
|
|
|
- width: 100%;
|
|
|
- overflow-y: auto; /* 添加垂直滚动条 */
|
|
|
- max-height: 100%; /* 设置最大高度 */
|
|
|
- /* 添加滚动条样式 */
|
|
|
- &::-webkit-scrollbar {
|
|
|
- width: rpx(1);
|
|
|
- }
|
|
|
- &::-webkit-scrollbar-thumb {
|
|
|
- background-color:#b4a8e1;
|
|
|
- border-radius: rpx(5);
|
|
|
- }
|
|
|
- &::-webkit-scrollbar-track {
|
|
|
- background-color: rgba(255, 255, 255, 0.2);
|
|
|
- border-radius: rpx(5);
|
|
|
- }
|
|
|
-}
|
|
|
-.drawer-box .toggle-button {
|
|
|
- width: rpx(10);
|
|
|
- height: rpx(50);
|
|
|
- font-size: rpx(7);
|
|
|
- background-color: rgba(17, 23, 29, 0.2);
|
|
|
- border: none;
|
|
|
- position: relative;
|
|
|
- writing-mode: vertical-lr; // 文字垂直排列,从左到右
|
|
|
- text-orientation: upright; // 文字保持正立
|
|
|
-}
|
|
|
-.toggle-button:hover {
|
|
|
- left: 0;
|
|
|
-}
|
|
|
+ // 文本生成图片
|
|
|
+ async textToImage(prompt, waitForCompletion = true) {
|
|
|
+ console.log('textToImage', prompt, waitForCompletion);
|
|
|
+
|
|
|
+ state.isProcessing = true;
|
|
|
+ try {
|
|
|
+ // 使用CreatePainting API创建图片任务
|
|
|
+ const createRes = await CreatePainting({
|
|
|
+ "modelId": state.modelId,
|
|
|
+ "prompt": prompt,
|
|
|
+ "width": 1024,
|
|
|
+ "height": 1024
|
|
|
+ });
|
|
|
+
|
|
|
+ // 记录任务ID到映射中
|
|
|
+ state.inProgressImageMap[createRes.data] = {id: createRes.data, status: AiImageStatusEnum.IN_PROGRESS};
|
|
|
+
|
|
|
+ // 开始轮询任务状态
|
|
|
+ if (!inProgressTimer.value) {
|
|
|
+ this.startImagePolling();
|
|
|
+ }
|
|
|
|
|
|
+ // 如果需要等待完成,等待图片生成完成
|
|
|
+ if (waitForCompletion) {
|
|
|
+ return await this.waitForImageCompletion(createRes.data);
|
|
|
+ }
|
|
|
|
|
|
-.toggle-button.is-active,
|
|
|
-.toggle-button:active,
|
|
|
-.toggle-button:focus {
|
|
|
- background-color: rgba(165, 209, 247, 0.8);
|
|
|
- color: white;
|
|
|
- border: none; // 移除点击时的边框
|
|
|
- outline: none; // 移除点击时的外边框
|
|
|
-}
|
|
|
+ return createRes.data; // 返回任务ID
|
|
|
+ } catch (error) {
|
|
|
+ console.error('生成图片失败:', error);
|
|
|
+ ElMessage.error('生成图片失败: ' + error.message);
|
|
|
+ return null;
|
|
|
+ } finally {
|
|
|
+ state.isProcessing = false;
|
|
|
+ }
|
|
|
+ },
|
|
|
|
|
|
-.box-1 {
|
|
|
- width: 100%;
|
|
|
- height: rpx(50);
|
|
|
- margin-top: rpx(10);
|
|
|
- display: flex;
|
|
|
- justify-content: center;
|
|
|
- align-items: center;
|
|
|
- box-sizing: border-box;
|
|
|
- font-size: rpx(15); // 默认字体大小
|
|
|
-}
|
|
|
+ // 【文生图】开始轮询图片生成状态
|
|
|
+ startImagePolling() {
|
|
|
+ if (inProgressTimer.value) {
|
|
|
+ clearInterval(inProgressTimer.value);
|
|
|
+ }
|
|
|
|
|
|
-.inner-box {
|
|
|
- height: 100%;
|
|
|
- display: flex;
|
|
|
- justify-content: center;
|
|
|
- align-items: center;
|
|
|
- font-size: rpx(16); // 默认字体大小
|
|
|
-}
|
|
|
+ inProgressTimer.value = setInterval(async () => {
|
|
|
+ await this.refreshWatchImages();
|
|
|
+ }, 3000); // 每3秒轮询一次
|
|
|
+ },
|
|
|
|
|
|
-.left-box {
|
|
|
- position: relative;
|
|
|
- justify-content: flex-start;
|
|
|
- align-items: flex-start;
|
|
|
- flex: 1; // 设置左侧盒子占比为 2
|
|
|
- cursor: pointer; // 添加鼠标指针样式
|
|
|
-}
|
|
|
-.box-icon {
|
|
|
- width: 100%;
|
|
|
- height: 100%;
|
|
|
- flex: 1;
|
|
|
- display: flex; // 添加 flex 布局
|
|
|
- align-items: center; // 垂直居中
|
|
|
- color: black; // 设置图标颜色为白色
|
|
|
- padding-left: rpx(15);
|
|
|
- font-size: rpx(10); // 设置图标大小,可按需调整
|
|
|
-}
|
|
|
-.box-icon .left-icon {
|
|
|
- margin-left: rpx(10);
|
|
|
- margin-right: rpx(5); // 设置图标和文字之间的间距 ;
|
|
|
-}
|
|
|
+ // 【文生图】轮询生成中的image列表
|
|
|
+ async refreshWatchImages() {
|
|
|
+ const imageIds = Object.keys(state.inProgressImageMap).map(Number);
|
|
|
+ if (imageIds.length === 0) {
|
|
|
+ if (inProgressTimer.value) {
|
|
|
+ clearInterval(inProgressTimer.value);
|
|
|
+ inProgressTimer.value = null;
|
|
|
+ }
|
|
|
+ return;
|
|
|
+ }
|
|
|
|
|
|
-.left-box span {
|
|
|
- position: absolute;
|
|
|
- font-size: rpx(11); // 默认字体大小
|
|
|
- color: black;
|
|
|
-}
|
|
|
+ try {
|
|
|
+ const list = await PaintingGetMys(imageIds);
|
|
|
+ const newWatchImages = {};
|
|
|
+
|
|
|
+ list.data.forEach((image) => {
|
|
|
+ if (image.status === AiImageStatusEnum.IN_PROGRESS) {
|
|
|
+ newWatchImages[image.id] = image;
|
|
|
+ } else if (image.status === AiImageStatusEnum.SUCCESS) {
|
|
|
+ // 图片生成完成,更新状态并显示预览
|
|
|
+ state.generatedContent.imageUrl = image.picUrl;
|
|
|
+ state.previewType = 'image';
|
|
|
+ state.previewContent = image.picUrl;
|
|
|
+ state.previewVisible = true;
|
|
|
+
|
|
|
+ // 打印到控制台,便于在结果区域显示
|
|
|
+ console.log('图片生成完成:', image.picUrl);
|
|
|
+ } else if (image.status === AiImageStatusEnum.FAIL) {
|
|
|
+ // 图片生成失败
|
|
|
+ ElMessage.error('图片生成失败: ' + (image.error || '未知错误'));
|
|
|
+ console.error('图片生成失败:', image.id, image.error);
|
|
|
+ }
|
|
|
+ });
|
|
|
|
|
|
-.right-box {
|
|
|
- flex: 1;
|
|
|
- position: relative; // 添加相对定位;
|
|
|
-}
|
|
|
-.top-right-box {
|
|
|
- position: absolute; // 添加绝对定位
|
|
|
- margin-top: rpx(20); // 调整上边距离
|
|
|
- margin-right: rpx(50); // 调整右边距离
|
|
|
- width: 100%; // 设置盒子宽度,可按需调整
|
|
|
- height: 60px; // 设置盒子高度,可按需调整
|
|
|
- display: flex;
|
|
|
- justify-content: flex-end;
|
|
|
-}
|
|
|
-.top-right-box {
|
|
|
- ::v-deep(.el-input__wrapper) {
|
|
|
- background-color: rgb(255, 255, 255, 0.5);
|
|
|
- border-radius: rpx(12);
|
|
|
- border: white 1px solid;
|
|
|
- color: #aaa5c5;
|
|
|
- }
|
|
|
- ::v-deep(.el-input__icon) {
|
|
|
- color: #aaa5c5; // 设置输入框图标颜色为白色
|
|
|
- }
|
|
|
- // 添加占位符样式
|
|
|
- ::v-deep(.el-input__inner::placeholder) {
|
|
|
- color: #aaa5c5;
|
|
|
- }
|
|
|
- // 添加输入框文字颜色样式
|
|
|
- ::v-deep(.el-input__inner) {
|
|
|
- color: black;
|
|
|
- }
|
|
|
-}
|
|
|
-// 搜索框
|
|
|
-.search-input {
|
|
|
- width: rpx(100);
|
|
|
- height: rpx(15);
|
|
|
- font-size: rpx(7);
|
|
|
-}
|
|
|
-.box-2 {
|
|
|
- width: 100%;
|
|
|
- flex: 1;
|
|
|
- box-shadow: 0 4px 8px rgba(202, 52, 52, 0.1);
|
|
|
- box-sizing: border-box;
|
|
|
- display: flex; // 确保子元素水平排列
|
|
|
- flex-wrap: wrap; // 允许子元素换行;
|
|
|
- align-content: center; // 顶部对齐;
|
|
|
- cursor: pointer; // 添加鼠标指针样式
|
|
|
-}
|
|
|
-.box-blockly {
|
|
|
- width: 60%;
|
|
|
- height: rpx(300);
|
|
|
- padding: 0px 20px;
|
|
|
- float: left;
|
|
|
-}
|
|
|
-.box-code{
|
|
|
- width: 35%;
|
|
|
- height: rpx(300);
|
|
|
+ state.inProgressImageMap = newWatchImages;
|
|
|
|
|
|
- .box-code-textarea{
|
|
|
- height: 80%;
|
|
|
- width: 100%;
|
|
|
- }
|
|
|
-}
|
|
|
+ // 如果没有正在处理的图片,清除定时器
|
|
|
+ if (Object.keys(newWatchImages).length === 0 && inProgressTimer.value) {
|
|
|
+ clearInterval(inProgressTimer.value);
|
|
|
+ inProgressTimer.value = null;
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('轮询图片状态失败:', error);
|
|
|
+ }
|
|
|
+ },
|
|
|
|
|
|
-/* 隐藏 Chrome 视频控件的渐变背景等默认样式 */
|
|
|
-video::-webkit-media-controls-panel {
|
|
|
- background: transparent !important; /* 去掉背景渐变,设为透明 */
|
|
|
-}
|
|
|
+ // 【文生图】等待图片生成完成
|
|
|
+ async 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.COMPLETED) {
|
|
|
+ clearInterval(checkInterval);
|
|
|
+ resolve(image.picUrl);
|
|
|
+ } else if (image.status === AiImageStatusEnum.FAILED) {
|
|
|
+ clearInterval(checkInterval);
|
|
|
+ reject(new Error(image.error || '图片生成失败'));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ clearInterval(checkInterval);
|
|
|
+ reject(error);
|
|
|
+ }
|
|
|
+ }, 3000);
|
|
|
+ });
|
|
|
+ },
|
|
|
|
|
|
-.small-title {
|
|
|
- width: 100%;
|
|
|
- margin-top: rpx(-20);
|
|
|
- height: rpx(20);
|
|
|
- color: black;
|
|
|
- font-size: rpx(10);
|
|
|
- justify-content: center; //使子元素水平居中
|
|
|
-}
|
|
|
-.video-switch {
|
|
|
- width: 100%;
|
|
|
- // height: rpx(50);
|
|
|
- display: flex;
|
|
|
- margin-top: rpx(-20);
|
|
|
- // background-color: saddlebrown;
|
|
|
-}
|
|
|
-.caret-right,
|
|
|
-.caret-left {
|
|
|
- width: rpx(50);
|
|
|
- margin: auto;
|
|
|
- display: flex;
|
|
|
- margin-top: rpx(5);
|
|
|
- margin-bottom: rpx(15);
|
|
|
-}
|
|
|
-.caret-left ::v-deep(.el-button.is-round),
|
|
|
-.caret-right ::v-deep(.el-button.is-round) {
|
|
|
- width: rpx(50);
|
|
|
- height: rpx(15);
|
|
|
- color: #aaa5c5;
|
|
|
- border-radius: none;
|
|
|
- border: 1px white solid;
|
|
|
- background-color: rgb(255, 255, 255, 0.5); // 进度条背景颜色,调浅为半透明白色
|
|
|
- box-shadow: 0 4px 8px rgba(202, 52, 52, 0.1);
|
|
|
-}
|
|
|
+ // 文本生成视频
|
|
|
+ async textToVideo(prompt, waitForCompletion = true) {
|
|
|
+ console.log('textToVideo', prompt, waitForCompletion);
|
|
|
+
|
|
|
+ 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 })
|
|
|
+ });
|
|
|
+
|
|
|
+ if (!initResponse.ok) throw new Error('初始化视频生成失败');
|
|
|
+
|
|
|
+ const initData = await initResponse.json();
|
|
|
+ const taskId = initData.taskId;
|
|
|
+
|
|
|
+ // 轮询结果
|
|
|
+ const checkResult = async () => {
|
|
|
+ const resultResponse = await fetch(`${aiServiceConfig.baseUrl}${aiServiceConfig.endpoints.textToVideo}/${taskId}`);
|
|
|
+ const resultData = await resultResponse.json();
|
|
|
+
|
|
|
+ if (resultData.status === 'completed') {
|
|
|
+ state.generatedContent.videoUrl = resultData.videoUrl;
|
|
|
+
|
|
|
+ // 显示预览
|
|
|
+ state.previewType = 'video';
|
|
|
+ state.previewContent = resultData.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();
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ return checkResult();
|
|
|
+ } catch (error) {
|
|
|
+ console.error('生成视频失败:', error);
|
|
|
+ ElMessage.error('生成视频失败: ' + error.message);
|
|
|
+ return null;
|
|
|
+ } finally {
|
|
|
+ state.isProcessing = false;
|
|
|
+ }
|
|
|
+ },
|
|
|
|
|
|
-// 儿童风格试题弹框样式
|
|
|
-.child-dialog {
|
|
|
- .el-dialog__header {
|
|
|
- display: none; // 隐藏原有的标题栏
|
|
|
+ // 文本生成文本(如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
|
|
|
+ })
|
|
|
+ });
|
|
|
+
|
|
|
+ if (!response.ok) throw new Error('AI处理失败');
|
|
|
+
|
|
|
+ const data = await response.json();
|
|
|
+ state.generatedContent.text = data.result;
|
|
|
+
|
|
|
+ // 显示预览
|
|
|
+ state.previewType = 'text';
|
|
|
+ state.previewContent = data.result;
|
|
|
+ state.previewVisible = true;
|
|
|
+
|
|
|
+ return data.result;
|
|
|
+ } catch (error) {
|
|
|
+ console.error('AI文本处理失败:', error);
|
|
|
+ ElMessage.error('AI文本处理失败: ' + error.message);
|
|
|
+ return null;
|
|
|
+ } finally {
|
|
|
+ state.isProcessing = false;
|
|
|
+ }
|
|
|
}
|
|
|
+};
|
|
|
|
|
|
- .el-dialog__body {
|
|
|
- padding: rpx(20);
|
|
|
- position: relative;
|
|
|
- }
|
|
|
+// 注册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}')`;
|
|
|
+ console.log('ai_voice_input', prompt, language);
|
|
|
+ return code;
|
|
|
+ };
|
|
|
|
|
|
- .el-dialog__footer {
|
|
|
- border-top: none;
|
|
|
- padding: rpx(10) rpx(20);
|
|
|
- text-align: center;
|
|
|
- }
|
|
|
+ // 文本生成图片
|
|
|
+ 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});`;
|
|
|
+ console.log('ai_text_to_image', prompt, waitForCompletion);
|
|
|
+ return code;
|
|
|
+ };
|
|
|
|
|
|
- .el-dialog__wrapper {
|
|
|
- // 修改半透明背景色
|
|
|
- background-color: rgba(0, 0, 0, 0.6);
|
|
|
- }
|
|
|
+ // 文本生成视频
|
|
|
+ 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});`;
|
|
|
+ console.log('ai_text_to_video', prompt, waitForCompletion);
|
|
|
+ return code;
|
|
|
+ };
|
|
|
|
|
|
- .el-dialog {
|
|
|
- border: none;
|
|
|
- border-radius: rpx(20);
|
|
|
- background: linear-gradient(
|
|
|
- 135deg,
|
|
|
- $light-color,
|
|
|
- #d8bfd8
|
|
|
- ); // 柔和的蓝紫色渐变
|
|
|
- // 添加边框边晕效果
|
|
|
- box-shadow: 0 0 20px 5px rgba($primary-color, 0.6),
|
|
|
- 0 0 40px 10px rgba($primary-color, 0.4),
|
|
|
- 0 0 60px 15px rgba($primary-color, 0.2);
|
|
|
- overflow: hidden;
|
|
|
- // 添加装饰元素
|
|
|
- &::before {
|
|
|
- content: '';
|
|
|
- position: absolute;
|
|
|
- top: 0;
|
|
|
- left: 0;
|
|
|
- width: 100%;
|
|
|
- height: rpx(10);
|
|
|
- background: linear-gradient(
|
|
|
- 90deg,
|
|
|
- $primary-color,
|
|
|
- $secondary-color,
|
|
|
- $accent-color
|
|
|
- );
|
|
|
- }
|
|
|
- }
|
|
|
+ // 文本生成文本
|
|
|
+ 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}')`;
|
|
|
+ console.log('ai_text_to_text', prompt, model);
|
|
|
+ return code;
|
|
|
+ };
|
|
|
}
|
|
|
|
|
|
-// 问题标题样式
|
|
|
-.question-title {
|
|
|
- background: linear-gradient(
|
|
|
- 90deg,
|
|
|
- rgba($primary-color, 0.2),
|
|
|
- rgba($secondary-color, 0.2)
|
|
|
- );
|
|
|
- padding: rpx(15);
|
|
|
- border-radius: rpx(12);
|
|
|
- margin-bottom: rpx(20);
|
|
|
- color: $accent-color;
|
|
|
- font-weight: bold;
|
|
|
- font-size: rpx(14);
|
|
|
- box-shadow: 0 rpx(3) rpx(10) rgba($primary-color, 0.1);
|
|
|
- position: relative;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
-
|
|
|
- .question-icon {
|
|
|
- background-color: $accent-color;
|
|
|
- color: white;
|
|
|
- width: rpx(24);
|
|
|
- height: rpx(24);
|
|
|
- border-radius: 50%;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- margin-right: rpx(10);
|
|
|
- font-weight: bold;
|
|
|
- box-shadow: 0 rpx(2) rpx(5) rgba($accent-color, 0.3);
|
|
|
- }
|
|
|
-}
|
|
|
+// 注册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;
|
|
|
+ };
|
|
|
|
|
|
-// 选项容器样式
|
|
|
-.options-container {
|
|
|
- margin-bottom: rpx(20);
|
|
|
-}
|
|
|
+ // 文本生成图片
|
|
|
+ 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;
|
|
|
+ };
|
|
|
|
|
|
-// 问题选项样式
|
|
|
-.question-option {
|
|
|
- margin: rpx(8) 0;
|
|
|
- padding: rpx(10) rpx(15);
|
|
|
- border-radius: rpx(12);
|
|
|
- border: rpx(1) solid rgba($primary-color, 0.3);
|
|
|
- transition: all 0.3s ease;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- background-color: white;
|
|
|
- box-shadow: 0 rpx(2) rpx(5) rgba($primary-color, 0.05);
|
|
|
+ // 文本生成视频
|
|
|
+ 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;
|
|
|
+ };
|
|
|
|
|
|
- ::v-deep(.el-radio__label) {
|
|
|
- color: $text-color;
|
|
|
- margin-left: rpx(8);
|
|
|
- flex: 1;
|
|
|
- text-align: left;
|
|
|
- // 增大字体大小
|
|
|
- font-size: rpx(12);
|
|
|
- }
|
|
|
+ // 文本生成文本
|
|
|
+ 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;
|
|
|
+ };
|
|
|
+}
|
|
|
|
|
|
- // 选中时的样式变化
|
|
|
- .el-radio__input.is-checked + .el-radio__label {
|
|
|
- font-weight: bold;
|
|
|
- color: $accent-color;
|
|
|
+// 生成代码
|
|
|
+const generateCode = (language) => {
|
|
|
+ if (!state.workspace) {
|
|
|
+ console.error('workspace 未正确初始化');
|
|
|
+ return;
|
|
|
}
|
|
|
|
|
|
- &:hover {
|
|
|
- background-color: rgba($primary-color, 0.05);
|
|
|
- border-color: rgba($primary-color, 0.5);
|
|
|
- transform: translateY(-rpx(1));
|
|
|
+ let generator;
|
|
|
+ if (language === 'javascript') {
|
|
|
+ generator = javascriptGenerator;
|
|
|
+ } else if (language === 'python') {
|
|
|
+ generator = pythonGenerator;
|
|
|
+ } else {
|
|
|
+ console.error('不支持的语言类型');
|
|
|
+ return;
|
|
|
}
|
|
|
-}
|
|
|
|
|
|
-// 暂无选项样式
|
|
|
-.no-options {
|
|
|
- color: rgba($text-color, 0.7);
|
|
|
- text-align: center;
|
|
|
- padding: rpx(20);
|
|
|
- font-size: rpx(12);
|
|
|
-}
|
|
|
+ const code = generator.workspaceToCode(state.workspace);
|
|
|
+ document.getElementById('textarea').value = code;
|
|
|
+};
|
|
|
|
|
|
-// 底部按钮样式
|
|
|
-.child-button {
|
|
|
- min-width: rpx(80);
|
|
|
- height: rpx(30);
|
|
|
- border-radius: rpx(15);
|
|
|
- font-size: rpx(12);
|
|
|
- font-weight: 500;
|
|
|
- transition: all 0.3s ease;
|
|
|
- box-shadow: 0 rpx(2) rpx(8) rgba(0, 0, 0, 0.1);
|
|
|
-
|
|
|
- &.confirm {
|
|
|
- background: linear-gradient(to right, $primary-color, $secondary-color);
|
|
|
- border: none;
|
|
|
- border-right: 15px;
|
|
|
- color: white;
|
|
|
-
|
|
|
- &:hover {
|
|
|
- background: linear-gradient(
|
|
|
- to right,
|
|
|
- color.adjust($primary-color, $lightness: -5%),
|
|
|
- color.adjust($secondary-color, $lightness: -5%)
|
|
|
- );
|
|
|
- box-shadow: 0 rpx(3) rpx(10) rgba($primary-color, 0.4);
|
|
|
- transform: translateY(-rpx(1));
|
|
|
- color: white;
|
|
|
- }
|
|
|
- }
|
|
|
+// 运行代码
|
|
|
+async function runCode() {
|
|
|
+ try {
|
|
|
+ const codeTextarea = document.getElementById('textarea');
|
|
|
+ const code = codeTextarea.value;
|
|
|
+ const resultElement = document.getElementById('run-result');
|
|
|
+
|
|
|
+ // 清空之前的结果
|
|
|
+ resultElement.innerHTML = '<div class="running-indicator">代码运行中...</div>';
|
|
|
+
|
|
|
+ // 保存原始console方法
|
|
|
+ const originalConsoleLog = console.log;
|
|
|
+ const originalConsoleError = console.error;
|
|
|
+ const originalConsoleWarn = console.warn;
|
|
|
+
|
|
|
+ // 创建输出缓冲区
|
|
|
+ let outputBuffer = '';
|
|
|
+
|
|
|
+ // 重定义console方法
|
|
|
+ console.log = (...args) => {
|
|
|
+ outputBuffer += '<div class="log-message">' + args.map(arg =>
|
|
|
+ typeof arg === 'object' ? JSON.stringify(arg) : arg
|
|
|
+ ).join(' ') + '</div>';
|
|
|
+ resultElement.innerHTML = outputBuffer;
|
|
|
+ originalConsoleLog.apply(console, args);
|
|
|
+ };
|
|
|
+
|
|
|
+ console.error = (...args) => {
|
|
|
+ outputBuffer += '<div class="error-message">' + args.map(arg =>
|
|
|
+ typeof arg === 'object' ? JSON.stringify(arg) : arg
|
|
|
+ ).join(' ') + '</div>';
|
|
|
+ resultElement.innerHTML = outputBuffer;
|
|
|
+ originalConsoleError.apply(console, args);
|
|
|
+ };
|
|
|
+
|
|
|
+ console.warn = (...args) => {
|
|
|
+ outputBuffer += '<div class="warn-message">' + args.map(arg =>
|
|
|
+ typeof arg === 'object' ? JSON.stringify(arg) : arg
|
|
|
+ ).join(' ') + '</div>';
|
|
|
+ resultElement.innerHTML = outputBuffer;
|
|
|
+ originalConsoleWarn.apply(console, args);
|
|
|
+ };
|
|
|
+
|
|
|
+ try {
|
|
|
+ // 添加安全检查
|
|
|
+ if (code.includes('eval(') || code.includes('Function(') ||
|
|
|
+ code.includes('document.write') || code.includes('window.location')) {
|
|
|
+ throw new Error('代码包含不安全的操作');
|
|
|
+ }
|
|
|
|
|
|
- &.cancel {
|
|
|
- background: white;
|
|
|
- border: rpx(1) solid rgba($primary-color, 0.3);
|
|
|
- color: $text-color;
|
|
|
+ // 包装代码为异步函数执行,支持await
|
|
|
+ const wrappedCode = `(async () => { ${code} })()`;
|
|
|
+ await new Function(wrappedCode)();
|
|
|
|
|
|
- &:hover {
|
|
|
- background: rgba($primary-color, 0.05);
|
|
|
- border-color: rgba($primary-color, 0.5);
|
|
|
- transform: translateY(-rpx(1));
|
|
|
+ if (outputBuffer === '') {
|
|
|
+ outputBuffer += '<div class="success-message">代码执行成功</div>';
|
|
|
+ resultElement.innerHTML = outputBuffer;
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ outputBuffer += `<div class="error-message">执行错误: ${error.message}</div>`;
|
|
|
+ resultElement.innerHTML = outputBuffer;
|
|
|
+ } finally {
|
|
|
+ // 恢复原始console方法
|
|
|
+ console.log = originalConsoleLog;
|
|
|
+ console.error = originalConsoleError;
|
|
|
+ console.warn = originalConsoleWarn;
|
|
|
}
|
|
|
+ } catch (err) {
|
|
|
+ console.error('运行代码时发生错误:', err);
|
|
|
+ document.getElementById('run-result').innerHTML =
|
|
|
+ `<div class="error-message">运行代码时发生错误: ${err.message}</div>`;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-// AI对话图标样式
|
|
|
-.ai-icon-container {
|
|
|
- position: absolute;
|
|
|
- bottom: rpx(20);
|
|
|
- right: rpx(20);
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- align-items: center;
|
|
|
- cursor: pointer;
|
|
|
- transition: all 0.3s ease;
|
|
|
+// 保存JSON
|
|
|
+function saveJson() {
|
|
|
+ const output = document.getElementById('textarea');
|
|
|
+ const json = Blockly.serialization.workspaces.save(state.workspace);
|
|
|
+ output.value = JSON.stringify(json, null, 2);
|
|
|
+ output.focus();
|
|
|
+ output.select();
|
|
|
+ taChange();
|
|
|
+}
|
|
|
|
|
|
- &:hover {
|
|
|
- transform: translateY(-rpx(2));
|
|
|
- }
|
|
|
+// 保存XML
|
|
|
+function saveXml() {
|
|
|
+ const output = document.getElementById('textarea');
|
|
|
+ const xml = Blockly.Xml.workspaceToDom(state.workspace);
|
|
|
+ output.value = Blockly.Xml.domToPrettyText(xml);
|
|
|
+ output.focus();
|
|
|
+ output.select();
|
|
|
+ taChange();
|
|
|
+}
|
|
|
|
|
|
- .ai-icon {
|
|
|
- width: rpx(30);
|
|
|
- height: rpx(30);
|
|
|
- margin-bottom: rpx(0);
|
|
|
- filter: drop-shadow(0 rpx(2) rpx(4) rgba($primary-color, 0.3));
|
|
|
- // 添加过渡动画
|
|
|
- transition: transform 0.3s ease;
|
|
|
- }
|
|
|
+// 重新加载
|
|
|
+function load() {
|
|
|
+ const input = document.getElementById('textarea');
|
|
|
+ if (!input.value) return;
|
|
|
|
|
|
- // 悬浮时放大效果
|
|
|
- .ai-icon:hover {
|
|
|
- transform: scale(1.5);
|
|
|
+ try {
|
|
|
+ const valid = saveIsValid(input.value);
|
|
|
+ if (valid.json) {
|
|
|
+ const parsedState = JSON.parse(input.value);
|
|
|
+ Blockly.serialization.workspaces.load(parsedState, state.workspace);
|
|
|
+ } else if (valid.xml) {
|
|
|
+ const xml = Blockly.utils.xml.textToDom(input.value);
|
|
|
+ Blockly.Xml.domToWorkspace(xml, state.workspace);
|
|
|
+ }
|
|
|
+ taChange();
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载失败:', error);
|
|
|
+ ElMessage.error('加载失败: ' + error.message);
|
|
|
}
|
|
|
+}
|
|
|
|
|
|
- .ai-text {
|
|
|
- color: $text-color;
|
|
|
- font-size: rpx(8);
|
|
|
- background-color: rgba(255, 255, 255, 0.7);
|
|
|
- padding: rpx(2) rpx(5);
|
|
|
- border-radius: rpx(5);
|
|
|
+function taChange() {
|
|
|
+ const textarea = document.getElementById('textarea');
|
|
|
+ if (sessionStorage) {
|
|
|
+ sessionStorage.setItem('textarea', textarea.value);
|
|
|
}
|
|
|
+ const valid = saveIsValid(textarea.value);
|
|
|
+ document.getElementById('import').disabled = !valid.json && !valid.xml;
|
|
|
}
|
|
|
|
|
|
-// AI消息样式
|
|
|
-.ai-message {
|
|
|
- display: flex;
|
|
|
- align-items: flex-start;
|
|
|
- margin-bottom: rpx(15);
|
|
|
-
|
|
|
- .ai-avatar {
|
|
|
- width: rpx(30);
|
|
|
- height: rpx(30);
|
|
|
- border-radius: 50%;
|
|
|
- margin-right: rpx(10);
|
|
|
- background-color: $primary-color;
|
|
|
- padding: rpx(5);
|
|
|
- box-shadow: 0 rpx(2) rpx(5) rgba($primary-color, 0.2);
|
|
|
+function saveIsValid(save) {
|
|
|
+ let validJson = true;
|
|
|
+ try {
|
|
|
+ JSON.parse(save);
|
|
|
+ } catch (e) {
|
|
|
+ validJson = false;
|
|
|
}
|
|
|
|
|
|
- .ai-text-content {
|
|
|
- background-color: $light-color;
|
|
|
- padding: rpx(8) rpx(12);
|
|
|
- border-radius: rpx(10);
|
|
|
- font-size: rpx(10);
|
|
|
- color: $text-color;
|
|
|
- max-width: 80%;
|
|
|
- box-shadow: 0 rpx(1) rpx(3) rgba(0, 0, 0, 0.05);
|
|
|
+ let validXml = true;
|
|
|
+ try {
|
|
|
+ Blockly.utils.xml.textToDom(save);
|
|
|
+ } catch (e) {
|
|
|
+ validXml = false;
|
|
|
}
|
|
|
+
|
|
|
+ return { json: validJson, xml: validXml };
|
|
|
}
|
|
|
|
|
|
-// 用户输入框样式
|
|
|
-.user-input {
|
|
|
- ::v-deep(.el-input__wrapper) {
|
|
|
- border-radius: rpx(15);
|
|
|
- border-color: rgba($primary-color, 0.3);
|
|
|
- margin-right: 10px;
|
|
|
+// 关闭预览
|
|
|
+function handleClosePreview() {
|
|
|
+ state.previewVisible = false;
|
|
|
+ state.previewContent = '';
|
|
|
+ state.previewType = '';
|
|
|
+}
|
|
|
|
|
|
- &:focus-within {
|
|
|
- box-shadow: 0 0 0 rpx(1) rgba($primary-color, 0.5);
|
|
|
- }
|
|
|
- }
|
|
|
+// 将aiService挂载到window,以便执行生成的代码时可以访问
|
|
|
+window.aiService = aiService;
|
|
|
+</script>
|
|
|
|
|
|
- ::v-deep(.el-input__inner) {
|
|
|
- font-size: rpx(10);
|
|
|
- color: $text-color;
|
|
|
- text-indent: 1em;
|
|
|
- }
|
|
|
-}
|
|
|
-/* 定义淡入和缩放动画 */
|
|
|
-.fade-scale-enter-active,
|
|
|
-.fade-scale-leave-active {
|
|
|
- transition: all 0.5s ease;
|
|
|
-}
|
|
|
+<style scoped lang="scss">
|
|
|
+/* 原有样式保持不变 */
|
|
|
+@use 'sass:math';
|
|
|
|
|
|
-.fade-scale-enter-from,
|
|
|
-.fade-scale-leave-to {
|
|
|
- opacity: 0.1;
|
|
|
- transform: scale(0.9);
|
|
|
+@function rpx($px) {
|
|
|
+ @return math.div($px, 750) * 100vw;
|
|
|
}
|
|
|
|
|
|
-// 自定义试题弹框背景
|
|
|
-.child-dialog-wrapper {
|
|
|
+.home-container {
|
|
|
position: fixed;
|
|
|
top: 0;
|
|
|
left: 0;
|
|
|
right: 0;
|
|
|
bottom: 0;
|
|
|
- background-color: rgba(0, 0, 0, 0.6); // 半透明背景
|
|
|
+ background: linear-gradient(to bottom, #001169, #b4a8e1);
|
|
|
display: flex;
|
|
|
- justify-content: center;
|
|
|
- align-items: center;
|
|
|
- z-index: 1000;
|
|
|
+ padding-top: rpx(10);
|
|
|
}
|
|
|
|
|
|
-.child-dialog {
|
|
|
- border: none;
|
|
|
- border-radius: rpx(20);
|
|
|
- background: linear-gradient(
|
|
|
- 135deg,
|
|
|
- $light-color,
|
|
|
- #d8bfd8
|
|
|
- ); // 柔和的蓝紫色渐变
|
|
|
- box-shadow: 0 0 20px 5px rgba($primary-color, 0.6),
|
|
|
- 0 0 40px 10px rgba($primary-color, 0.4),
|
|
|
- 0 0 60px 15px rgba($primary-color, 0.2);
|
|
|
- overflow: hidden;
|
|
|
- padding: rpx(20);
|
|
|
+.box-blockly {
|
|
|
width: 60%;
|
|
|
- position: relative;
|
|
|
-}
|
|
|
-
|
|
|
-// AI对话弹框样式
|
|
|
-.ai-dialog-wrapper {
|
|
|
- position: fixed;
|
|
|
- top: 0;
|
|
|
- left: 0;
|
|
|
- right: 0;
|
|
|
- bottom: 0;
|
|
|
- display: flex;
|
|
|
- justify-content: flex-end;
|
|
|
- align-items: center;
|
|
|
- z-index: 1001;
|
|
|
- pointer-events: none;
|
|
|
+ height: 90%;
|
|
|
+ padding: rpx(10) rpx(10);
|
|
|
+ float: left;
|
|
|
}
|
|
|
|
|
|
-.ai-dialog {
|
|
|
- border: none;
|
|
|
- border-radius: rpx(20);
|
|
|
- background: linear-gradient(135deg, $light-color, #d8bfd8);
|
|
|
- box-shadow: 0 0 20px 5px rgba($primary-color, 0.6),
|
|
|
- 0 0 40px 10px rgba($primary-color, 0.4),
|
|
|
- 0 0 60px 15px rgba($primary-color, 0.2);
|
|
|
- overflow: hidden;
|
|
|
- padding: rpx(20);
|
|
|
- width: 30%;
|
|
|
- // 增加高度
|
|
|
- height: 80%;
|
|
|
- margin-right: rpx(50);
|
|
|
- pointer-events: auto;
|
|
|
- position: relative;
|
|
|
+.box-code {
|
|
|
+ color: white;
|
|
|
+ width: 35%;
|
|
|
+ height: 90%;
|
|
|
+ padding: rpx(10) rpx(10);
|
|
|
display: flex;
|
|
|
flex-direction: column;
|
|
|
|
|
|
- &::before {
|
|
|
- content: '';
|
|
|
- position: absolute;
|
|
|
- top: 0;
|
|
|
- left: 0;
|
|
|
- width: 100%;
|
|
|
- height: rpx(10);
|
|
|
- background: linear-gradient(
|
|
|
- 90deg,
|
|
|
- $primary-color,
|
|
|
- $secondary-color,
|
|
|
- $accent-color
|
|
|
- );
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-.ai-dialog-header {
|
|
|
- display: flex;
|
|
|
- justify-content: space-between;
|
|
|
- align-items: center;
|
|
|
- margin-bottom: rpx(15);
|
|
|
-
|
|
|
- h3 {
|
|
|
- color: $text-color;
|
|
|
- font-size: rpx(12);
|
|
|
+ .button-container {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 8px;
|
|
|
+ align-items: flex-start;
|
|
|
}
|
|
|
|
|
|
- .close-btn {
|
|
|
- padding: 0;
|
|
|
- width: rpx(20);
|
|
|
- height: rpx(20);
|
|
|
- font-size: rpx(16);
|
|
|
- line-height: 1;
|
|
|
+ .button-container >>> .el-button {
|
|
|
+ flex-shrink: 0;
|
|
|
}
|
|
|
-}
|
|
|
|
|
|
-.ai-dialog-content {
|
|
|
- flex: 1;
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
-}
|
|
|
-
|
|
|
-.ai-message-history {
|
|
|
- flex: 1;
|
|
|
- // 当内容超出容器高度时,显示垂直滚动条
|
|
|
- overflow-y: auto;
|
|
|
- margin-bottom: rpx(15);
|
|
|
- // 可以根据实际情况调整最大高度
|
|
|
- max-height: 50vh;
|
|
|
- padding: 5px 10px;
|
|
|
-
|
|
|
- .message {
|
|
|
+ .code-section {
|
|
|
+ flex: 1;
|
|
|
display: flex;
|
|
|
- align-items: flex-start;
|
|
|
- margin-bottom: rpx(10);
|
|
|
-
|
|
|
- &.user {
|
|
|
- flex-direction: row-reverse;
|
|
|
- }
|
|
|
-
|
|
|
- .avatar {
|
|
|
- width: rpx(30);
|
|
|
- height: rpx(30);
|
|
|
- border-radius: 50%;
|
|
|
- margin: 0 rpx(10);
|
|
|
- }
|
|
|
-
|
|
|
- .user {
|
|
|
- width: 15px;
|
|
|
- height: 15px;
|
|
|
+ flex-direction: column;
|
|
|
+ margin-bottom: 10px;
|
|
|
+
|
|
|
+ .box-code-textarea {
|
|
|
+ flex: 1;
|
|
|
+ resize: none;
|
|
|
+ background-color: #f8f9fa;
|
|
|
+ border: 1px solid #ddd;
|
|
|
+ border-radius: 4px;
|
|
|
+ padding: 10px;
|
|
|
+ overflow-y: auto;
|
|
|
+ font-family: monospace;
|
|
|
+ font-size: 14px;
|
|
|
}
|
|
|
-
|
|
|
- .message-content {
|
|
|
- background-color: $light-color;
|
|
|
- padding: rpx(8) rpx(12);
|
|
|
- border-radius: rpx(10);
|
|
|
- font-size: rpx(10);
|
|
|
- color: $text-color;
|
|
|
- max-width: 80%;
|
|
|
- box-shadow: 0 rpx(1) rpx(3) rgba(0, 0, 0, 0.05);
|
|
|
- }
|
|
|
- }
|
|
|
- // 滚动条整体样式
|
|
|
- &::-webkit-scrollbar {
|
|
|
- width: rpx(4); // 滚动条宽度
|
|
|
}
|
|
|
|
|
|
- // 滚动条滑块样式
|
|
|
- &::-webkit-scrollbar-thumb {
|
|
|
- background-color: $primary-color; // 滑块颜色
|
|
|
- border-radius: rpx(4); // 滑块圆角
|
|
|
- transition: background-color 0.3s ease; // 颜色过渡效果
|
|
|
-
|
|
|
- &:hover {
|
|
|
- //background-color: darken($primary-color, 10%); // 悬停时滑块颜色加深
|
|
|
+ .result-section {
|
|
|
+ flex: 1;
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+
|
|
|
+ .run-result-content {
|
|
|
+ flex: 1;
|
|
|
+ background-color: #f8f9fa;
|
|
|
+ border: 1px solid #ddd;
|
|
|
+ border-radius: 4px;
|
|
|
+ padding: 10px;
|
|
|
+ overflow-y: auto;
|
|
|
+ font-family: monospace;
|
|
|
+ font-size: 14px;
|
|
|
}
|
|
|
}
|
|
|
+}
|
|
|
|
|
|
- // 滚动条轨道样式
|
|
|
- &::-webkit-scrollbar-track {
|
|
|
- background-color: rgba($primary-color, 0.2); // 轨道颜色
|
|
|
- border-radius: rpx(4); // 轨道圆角
|
|
|
- }
|
|
|
-
|
|
|
- .message {
|
|
|
- display: flex;
|
|
|
- align-items: flex-start;
|
|
|
- margin-bottom: rpx(10);
|
|
|
+// AI模块样式
|
|
|
+[categorystyle="ai_category"] > .blocklyTreeRow {
|
|
|
+ background-color: #9c27b0 !important;
|
|
|
+}
|
|
|
|
|
|
- &.user {
|
|
|
- flex-direction: row-reverse;
|
|
|
- }
|
|
|
+// 运行结果样式
|
|
|
+.running-indicator { color: #666; }
|
|
|
+.log-message { color: #333; }
|
|
|
+.error-message { color: #dc3545; }
|
|
|
+.warn-message { color: #ffc107; }
|
|
|
+.success-message { color: #28a745; }
|
|
|
|
|
|
- .avatar {
|
|
|
- width: rpx(30);
|
|
|
- height: rpx(30);
|
|
|
- border-radius: 50%;
|
|
|
- margin: 0 rpx(10);
|
|
|
- }
|
|
|
+// 预览样式
|
|
|
+.preview-image-container, .preview-video-container {
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+}
|
|
|
|
|
|
- .message-content {
|
|
|
- background-color: $light-color;
|
|
|
- padding: rpx(8) rpx(12);
|
|
|
- border-radius: rpx(10);
|
|
|
- font-size: rpx(10);
|
|
|
- color: $text-color;
|
|
|
- max-width: 80%;
|
|
|
- box-shadow: 0 rpx(1) rpx(3) rgba(0, 0, 0, 0.05);
|
|
|
- }
|
|
|
- }
|
|
|
+.preview-image, .preview-video {
|
|
|
+ max-width: 100%;
|
|
|
+ max-height: 60vh;
|
|
|
+ border-radius: 4px;
|
|
|
}
|
|
|
|
|
|
-// 优化发送按钮样式
|
|
|
-.send-button {
|
|
|
- //background: linear-gradient(90deg, $primary-color, $secondary-color);
|
|
|
- border: none;
|
|
|
- color: white;
|
|
|
+.preview-text-container {
|
|
|
+ max-height: 60vh;
|
|
|
+ overflow-y: auto;
|
|
|
+ padding: 10px;
|
|
|
+ background-color: #f5f5f5;
|
|
|
+ border-radius: 4px;
|
|
|
+}
|
|
|
|
|
|
- &:hover {
|
|
|
- //background: linear-gradient(90deg, darken($primary-color, 5%), darken($secondary-color, 5%));
|
|
|
- }
|
|
|
+//【文生图预览】
|
|
|
+.extra-image-preview {
|
|
|
+ margin-top: 10px;
|
|
|
+ padding: 10px;
|
|
|
+ border: 1px solid #ddd;
|
|
|
+ border-radius: 5px;
|
|
|
+ background-color: #f9f9f9;
|
|
|
}
|
|
|
-.el-menu .el-menu-item.is-active {
|
|
|
- background: linear-gradient(to bottom, #fee78a, #ffce1b);
|
|
|
- color: black;
|
|
|
- font-size: rpx(8);
|
|
|
- box-shadow: 0 4px 8px rgba(3, 3, 3, 0.3);
|
|
|
+
|
|
|
+.extra-preview-image {
|
|
|
+ max-width: 100%;
|
|
|
+ max-height: 400px;
|
|
|
+ border-radius: 4px;
|
|
|
}
|
|
|
-</style>
|
|
|
+</style>
|