diff --git a/boards/default_src/python_pyodide/blocks/tensorflow.js b/boards/default_src/python_pyodide/blocks/tensorflow.js new file mode 100644 index 00000000..6ac54c7c --- /dev/null +++ b/boards/default_src/python_pyodide/blocks/tensorflow.js @@ -0,0 +1,230 @@ +import * as Blockly from 'blockly/core'; + +const TENSORFLOW_HUE = '#1216ab'; + +Blockly.Blocks.tensorflow_init_tensor = { + init: function () { + this.appendValueInput("VAR") + .setCheck(null) + .setAlign(Blockly.ALIGN_LEFT) + .appendField(Blockly.Msg.MIXLY_TENSORFLOW_INIT_TENSOR); + this.setOutput(true, null); + this.setColour(TENSORFLOW_HUE); + this.setTooltip(''); + this.setHelpUrl(''); + } +}; + +Blockly.Blocks.tensorflow_sequential = { + init: function () { + this.appendDummyInput() + .setAlign(Blockly.ALIGN_LEFT) + .appendField(Blockly.Msg.MIXLY_TENSORFLOW_SEQUENTIAL); + this.setOutput(true, null); + this.setColour(TENSORFLOW_HUE); + this.setTooltip(''); + this.setHelpUrl(''); + } +}; + +Blockly.Blocks.tensorflow_layers_dense = { + init: function () { + this.appendDummyInput() + .setAlign(Blockly.ALIGN_LEFT) + .appendField(Blockly.Msg.MIXLY_TENSORFLOW_INIT_LAYERS_DENSE_LAYER); + this.appendValueInput("VAR1") + .setCheck(null) + .setAlign(Blockly.ALIGN_RIGHT) + .appendField(Blockly.Msg.MIXLY_TENSORFLOW_OUTPUT_DIMENSION); + this.appendValueInput("VAR2") + .setCheck(null) + .setAlign(Blockly.ALIGN_RIGHT) + .appendField(Blockly.Msg.MIXLY_TENSORFLOW_INPUT_SHAPE); + this.setOutput(true, null); + this.setColour(TENSORFLOW_HUE); + this.setTooltip(''); + this.setHelpUrl(''); + } +}; + +Blockly.Blocks.tensorflow_add = { + init: function () { + this.appendValueInput("VAR1") + .setCheck(null) + .setAlign(Blockly.ALIGN_LEFT) + .appendField(Blockly.Msg.MIXLY_TENSORFLOW_MODEL); + this.appendValueInput("VAR2") + .setCheck(null) + .setAlign(Blockly.ALIGN_LEFT) + .appendField(Blockly.Msg.MIXLY_TENSORFLOW_ADD_LAYER); + this.setInputsInline(true); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(TENSORFLOW_HUE); + this.setTooltip(''); + this.setHelpUrl(''); + } +}; + +Blockly.Blocks.tensorflow_compile = { + init: function () { + this.appendValueInput("VAR1") + .setCheck(null) + .setAlign(Blockly.ALIGN_LEFT) + .appendField(Blockly.Msg.MIXLY_TENSORFLOW_COMPILE_MODEL); + this.appendDummyInput() + .setAlign(Blockly.ALIGN_LEFT) + .appendField(Blockly.Msg.MIXLY_TENSORFLOW_LOSS_FUNCTION_TYPE) + .appendField(new Blockly.FieldDropdown([ + [Blockly.Msg.MIXLY_TENSORFLOW_MEAN_SQUARED_ERROR, "meanSquaredError"] + ]), "VAR2"); + this.appendDummyInput() + .setAlign(Blockly.ALIGN_LEFT) + .appendField(Blockly.Msg.MIXLY_TENSORFLOW_OPTIMIZER) + .appendField(new Blockly.FieldDropdown([ + [Blockly.Msg.MIXLY_TENSORFLOW_SGD, "sgd"] + ]), "VAR3"); + this.setInputsInline(true); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(TENSORFLOW_HUE); + this.setTooltip(''); + this.setHelpUrl(''); + } +}; + +Blockly.Blocks.tensorflow_fit = { + init: function () { + this.appendValueInput("VAR1") + .setCheck(null) + .setAlign(Blockly.ALIGN_LEFT) + .appendField(Blockly.Msg.MIXLY_TENSORFLOW_FIT_MODEL); + this.appendValueInput("VAR2") + .setCheck(null) + .setAlign(Blockly.ALIGN_RIGHT) + .appendField(Blockly.Msg.MIXLY_TENSORFLOW_FIT_INPUT_DATA); + this.appendValueInput("VAR3") + .setCheck(null) + .setAlign(Blockly.ALIGN_RIGHT) + .appendField(Blockly.Msg.MIXLY_TENSORFLOW_FIT_TARGET_DATA); + this.appendValueInput("VAR4") + .setCheck(null) + .setAlign(Blockly.ALIGN_RIGHT) + .appendField(Blockly.Msg.MIXLY_TENSORFLOW_FIT_EPOCHS); + this.appendValueInput("VAR5") + .setCheck(null) + .setAlign(Blockly.ALIGN_RIGHT) + .appendField(Blockly.Msg.MIXLY_TENSORFLOW_FIT_VERBOSE); + this.appendDummyInput() + .setAlign(Blockly.ALIGN_LEFT) + .appendField(Blockly.Msg.MIXLY_TENSORFLOW_FIT_RETURN_HISTORY); + this.setInputsInline(false); + this.setOutput(true, null); + this.setColour(TENSORFLOW_HUE); + this.setTooltip(''); + this.setHelpUrl(''); + } +}; + +Blockly.Blocks.tensorflow_get_loss = { + init: function () { + this.appendValueInput("VAR") + .setCheck(null) + .setAlign(Blockly.ALIGN_LEFT) + .appendField(Blockly.Msg.MIXLY_TENSORFLOW_GET_LOSS_FROM_HISTORY); + this.appendDummyInput() + .setAlign(Blockly.ALIGN_LEFT) + .appendField(Blockly.Msg.MIXLY_TENSORFLOW_GET_LOSS_FROM_HISTORY_2); + this.setOutput(true, null); + this.setColour(TENSORFLOW_HUE); + this.setTooltip(''); + this.setHelpUrl(''); + } +}; + +Blockly.Blocks.tensorflow_predict = { + init: function () { + this.appendValueInput("VAR1") + .setCheck(null) + .setAlign(Blockly.ALIGN_LEFT) + .appendField(Blockly.Msg.MIXLY_TENSORFLOW_PREDICT); + this.appendValueInput("VAR2") + .setCheck(null) + .setAlign(Blockly.ALIGN_RIGHT) + .appendField(Blockly.Msg.MIXLY_TENSORFLOW_PREDICT_INPUT_DATA); + this.appendDummyInput() + .setAlign(Blockly.ALIGN_RIGHT) + .appendField(Blockly.Msg.MIXLY_TENSORFLOW_PREDICT_RETURN_RESULT); + this.setInputsInline(false); + this.setOutput(true, null); + this.setColour(TENSORFLOW_HUE); + this.setTooltip(''); + this.setHelpUrl(''); + } +}; + +Blockly.Blocks.tensorflow_get_tensor_data = { + init: function () { + this.appendValueInput("VAR") + .setCheck(null) + .setAlign(Blockly.ALIGN_LEFT) + .appendField(Blockly.Msg.MIXLY_TENSORFLOW_GET_TENSOR_DATA); + this.setOutput(true, null); + this.setColour(TENSORFLOW_HUE); + this.setTooltip(''); + this.setHelpUrl(''); + } +}; + +Blockly.Blocks.tensorflow_save_or_export_model = { + init: function () { + this.appendValueInput("NAME1") + .setCheck(null) + .setAlign(Blockly.ALIGN_LEFT) + .appendField(Blockly.Msg.MIXLY_TENSORFLOW_MODEL); + this.appendValueInput("NAME2") + .setCheck(null) + .setAlign(Blockly.ALIGN_LEFT) + .appendField(new Blockly.FieldDropdown([ + [Blockly.Msg.MIXLY_TENSORFLOW_SAVE_MODEL, "save"], + [Blockly.Msg.MIXLY_TENSORFLOW_EXPORT_MODEL, "export"] + ]), "NAME") + .appendField(Blockly.Msg.MIXLY_TENSORFLOW_SAVE_MODEL_NAME); + this.setInputsInline(true); + this.setPreviousStatement(true, null); + this.setNextStatement(true, null); + this.setColour(TENSORFLOW_HUE); + this.setTooltip(''); + this.setHelpUrl(''); + } +}; + +Blockly.Blocks.tensorflow_use_load_model = { + init: function () { + this.appendValueInput("NAME") + .setCheck(null) + .setAlign(Blockly.ALIGN_LEFT) + .appendField(Blockly.Msg.MIXLY_TENSORFLOW_LOAD_MODEL) + .appendField(Blockly.Msg.MIXLY_TENSORFLOW_MODEL_NAME); + this.setInputsInline(true); + this.setOutput(true, null); + this.setColour(TENSORFLOW_HUE); + this.setTooltip(''); + this.setHelpUrl(''); + } +}; + +Blockly.Blocks.tensorflow_prepare_picture = { + init: function () { + this.appendValueInput("NAME") + .setCheck(null) + .setAlign(Blockly.ALIGN_LEFT) + .appendField(Blockly.Msg.MIXLY_TENSORFLOW_PREPARE_PICTURE_TO_TENSOR) + .appendField(Blockly.Msg.MIXLY_TENSORFLOW_PREPARE_PICTURE_READ_PICTURE); + this.setInputsInline(true); + this.setOutput(true, null); + this.setColour(TENSORFLOW_HUE); + this.setTooltip(''); + this.setHelpUrl(''); + } +}; \ No newline at end of file diff --git a/boards/default_src/python_pyodide/export.js b/boards/default_src/python_pyodide/export.js index 70d64194..ccf9bf50 100644 --- a/boards/default_src/python_pyodide/export.js +++ b/boards/default_src/python_pyodide/export.js @@ -1,7 +1,11 @@ import * as PythonPyodideSKLearnBlocks from './blocks/sklearn'; import * as PythonPyodideSKLearnGenerators from './generators/sklearn'; +import * as PythonTensorflowBlocks from './blocks/tensorflow'; +import * as PythonTensorflowGenerators from './generators/tensorflow'; export { PythonPyodideSKLearnBlocks, - PythonPyodideSKLearnGenerators + PythonPyodideSKLearnGenerators, + PythonTensorflowBlocks, + PythonTensorflowGenerators }; \ No newline at end of file diff --git a/boards/default_src/python_pyodide/generators/tensorflow.js b/boards/default_src/python_pyodide/generators/tensorflow.js new file mode 100644 index 00000000..404c3b7f --- /dev/null +++ b/boards/default_src/python_pyodide/generators/tensorflow.js @@ -0,0 +1,97 @@ +export const tensorflow_init_tensor = function (_, generator) { + var VALUE_INPUT_VAR = generator.valueToCode(this, "VAR", generator.ORDER_ATOMIC); + generator.definitions_['import_tensorflow'] = 'import tensorflow'; + var code = 'tensorflow.tensor(' + VALUE_INPUT_VAR + ')'; + return [code, generator.ORDER_ATOMIC]; +}; + +export const tensorflow_sequential = function (_, generator) { + generator.definitions_['import_tensorflow'] = 'import tensorflow'; + var code = 'tensorflow.sequential()'; + return [code, generator.ORDER_ATOMIC]; +}; + +export const tensorflow_layers_dense = function (_, generator) { + generator.definitions_['import_tensorflow'] = 'import tensorflow'; + var VALUE_INPUT_VAR1 = generator.valueToCode(this, "VAR1", generator.ORDER_ATOMIC); + var VALUE_INPUT_VAR2 = generator.valueToCode(this, "VAR2", generator.ORDER_ATOMIC); + var code = 'tensorflow.layers.dense(units = ' + VALUE_INPUT_VAR1 + ', input_shape = ' + VALUE_INPUT_VAR2 + ')'; + return [code, generator.ORDER_ATOMIC]; +}; + +export const tensorflow_add = function (_, generator) { + generator.definitions_['import_tensorflow'] = 'import tensorflow'; + var VALUE_INPUT_VAR1 = generator.valueToCode(this, "VAR1", generator.ORDER_ATOMIC); + var VALUE_INPUT_VAR2 = generator.valueToCode(this, "VAR2", generator.ORDER_ATOMIC); + var code = VALUE_INPUT_VAR1 + '.add(' + VALUE_INPUT_VAR2 + ')\n'; + return code; +}; + +export const tensorflow_compile = function (_, generator) { + generator.definitions_['import_tensorflow'] = 'import tensorflow'; + var VALUE_INPUT_VAR1 = generator.valueToCode(this, "VAR1", generator.ORDER_ATOMIC); + var VALUE_INPUT_VAR2 = this.getFieldValue("VAR2"); + var VALUE_INPUT_VAR3 = this.getFieldValue("VAR3"); + var code = VALUE_INPUT_VAR1 + '.compile(loss = "' + VALUE_INPUT_VAR2 + '", optimizer = "' + VALUE_INPUT_VAR3 + '")\n'; + return code; +} + +export const tensorflow_fit = function (_, generator) { + generator.definitions_['import_tensorflow'] = 'import tensorflow'; + var VALUE_INPUT_VAR1 = generator.valueToCode(this, "VAR1", generator.ORDER_ATOMIC); + var VALUE_INPUT_VAR2 = generator.valueToCode(this, "VAR2", generator.ORDER_ATOMIC); + var VALUE_INPUT_VAR3 = generator.valueToCode(this, "VAR3", generator.ORDER_ATOMIC); + var VALUE_INPUT_VAR4 = generator.valueToCode(this, "VAR4", generator.ORDER_ATOMIC); + var VALUE_INPUT_VAR5 = generator.valueToCode(this, "VAR5", generator.ORDER_ATOMIC); + var code = 'await ' + VALUE_INPUT_VAR1 + '.fit(' + VALUE_INPUT_VAR2 + ', ' + VALUE_INPUT_VAR3 + ', epochs=' + VALUE_INPUT_VAR4 + ', verbose=' + VALUE_INPUT_VAR5 + ')'; + return [code, generator.ORDER_ATOMIC]; +}; + +export const tensorflow_get_loss = function (_, generator) { + generator.definitions_['import_tensorflow'] = 'import tensorflow'; + var VALUE_INPUT_VAR = generator.valueToCode(this, "VAR", generator.ORDER_ATOMIC); + var code = VALUE_INPUT_VAR + '.history.loss'; + return [code, generator.ORDER_ATOMIC]; +}; + +export const tensorflow_predict = function (_, generator) { + generator.definitions_['import_tensorflow'] = 'import tensorflow'; + var VALUE_INPUT_VAR1 = generator.valueToCode(this, "VAR1", generator.ORDER_ATOMIC); + var VALUE_INPUT_VAR2 = generator.valueToCode(this, "VAR2", generator.ORDER_ATOMIC); + var code = VALUE_INPUT_VAR1 + '.predict(' + VALUE_INPUT_VAR2 + ')'; + return [code, generator.ORDER_ATOMIC]; +}; + +export const tensorflow_get_tensor_data = function (_, generator) { + generator.definitions_['import_tensorflow'] = 'import tensorflow'; + var VALUE_INPUT_VAR1 = generator.valueToCode(this, "VAR", generator.ORDER_ATOMIC); + var code = "(await " + VALUE_INPUT_VAR1 + ".data())"; + return [code, generator.ORDER_ATOMIC]; +}; + +export const tensorflow_save_or_export_model = function (_, generator) { + generator.definitions_['import_tensorflow'] = 'import tensorflow'; + + var VALUE_INPUT_NAME1 = generator.valueToCode(this, "NAME1", generator.ORDER_ATOMIC); + var FIELD_NAME = this.getFieldValue("NAME"); + var VALUE_INPUT_NAME2 = generator.valueToCode(this, "NAME2", generator.ORDER_ATOMIC).replace(/^'|'$/g, ''); + if (FIELD_NAME == "export") { + return `await ${VALUE_INPUT_NAME1}.save("downloads://${VALUE_INPUT_NAME2}")\n`; + } + return `await ${VALUE_INPUT_NAME1}.save("indexeddb://${VALUE_INPUT_NAME2}")\n`; +}; + +export const tensorflow_use_load_model = function (_, generator) { + generator.definitions_['import_tensorflow'] = 'import tensorflow'; + + var VALUE_INPUT_NAME = generator.valueToCode(this, "NAME", generator.ORDER_ATOMIC).replace(/^'|'$/g, ''); + return [`await tensorflow.load_model("${VALUE_INPUT_NAME}")`, generator.ORDER_ATOMIC]; +}; + +export const tensorflow_prepare_picture = function (_, generator) { + generator.definitions_['import_tensorflow'] = 'import tensorflow'; + generator.definitions_['import_numpy'] = 'import numpy'; + generator.definitions_['import_PIL'] = 'import PIL'; + var VALUE_INPUT_NAME = generator.valueToCode(this, "NAME", generator.ORDER_ATOMIC); + return [`(await tensorflow.prepare_qmyixtxi(tensorflow.tensor(numpy.array(PIL.Image.open(${VALUE_INPUT_NAME}).convert('RGB')))))`, generator.ORDER_ATOMIC]; +}; diff --git a/boards/default_src/python_pyodide/index.js b/boards/default_src/python_pyodide/index.js index 85c308b0..72f9bfd4 100644 --- a/boards/default_src/python_pyodide/index.js +++ b/boards/default_src/python_pyodide/index.js @@ -69,7 +69,9 @@ import { import { PythonPyodideSKLearnBlocks, - PythonPyodideSKLearnGenerators + PythonPyodideSKLearnGenerators, + PythonTensorflowBlocks, + PythonTensorflowGenerators } from './'; import './others/loader'; @@ -113,7 +115,8 @@ Object.assign( PythonMixpySKLearnBlocks, PythonMixpySystemBlocks, PythonMixpyTurtleBlocks, - PythonPyodideSKLearnBlocks + PythonPyodideSKLearnBlocks, + PythonTensorflowBlocks ); Object.assign( @@ -146,5 +149,6 @@ Object.assign( PythonMixpySKLearnGenerators, PythonMixpySystemGenerators, PythonMixpyTurtleGenerators, - PythonPyodideSKLearnGenerators + PythonPyodideSKLearnGenerators, + PythonTensorflowGenerators ); \ No newline at end of file diff --git a/boards/default_src/python_pyodide/origin/deps/0.62.21/python3/modules/tensorflow-0.0.1-py3-none-any.whl b/boards/default_src/python_pyodide/origin/deps/0.62.21/python3/modules/tensorflow-0.0.1-py3-none-any.whl new file mode 100644 index 00000000..d26e3fed Binary files /dev/null and b/boards/default_src/python_pyodide/origin/deps/0.62.21/python3/modules/tensorflow-0.0.1-py3-none-any.whl differ diff --git a/boards/default_src/python_pyodide/origin/deps/0.62.21/python3/repodata.json b/boards/default_src/python_pyodide/origin/deps/0.62.21/python3/repodata.json index 001357ad..986b0714 100644 --- a/boards/default_src/python_pyodide/origin/deps/0.62.21/python3/repodata.json +++ b/boards/default_src/python_pyodide/origin/deps/0.62.21/python3/repodata.json @@ -3378,6 +3378,17 @@ "sprite" ], "depends": [] + }, + "tensorflow": { + "name": "tensorflow", + "version": "0.0.1", + "file_name": "{basthonRoot}/modules/tensorflow-0.0.1-py3-none-any.whl", + "install_dir": "site", + "sha256": "734415520a240e19f44c6121f0e237300c9f82ce87635a9e40a6639d0f35c888", + "imports": [ + "tensorflow" + ], + "depends": [] } } } \ No newline at end of file diff --git a/boards/default_src/python_pyodide/others/loader.js b/boards/default_src/python_pyodide/others/loader.js index 5de725d1..1700189d 100644 --- a/boards/default_src/python_pyodide/others/loader.js +++ b/boards/default_src/python_pyodide/others/loader.js @@ -2,5 +2,328 @@ import NavExt from './nav-ext'; import * as tf from '@tensorflow/tfjs'; import './tensorflow'; +import * as Blockly from 'blockly/core'; NavExt.init(); -window.tf = tf; \ No newline at end of file +window.tf = tf; + +let featureExtractor; +// featureExtractor = await tf.loadGraphModel("../common/media/tfmodel/model.json"); +// window.featureExtractor = featureExtractor; + +function closeModal() { + document.getElementById('modalOverlay').style.display = 'none'; +} + +// 从IndexedDB删除单个模型 +async function deleteModel(modelName) { + try { + await tf.io.removeModel(`indexeddb://${modelName}`); + // 从UI移除 + const modelItem = document.querySelector(`.model-item[data-model-name="${modelName}"]`); + if (modelItem) modelItem.remove(); + } catch (error) { + console.error('删除模型失败:', error); + alert('删除模型失败: ' + error.message); + } +} + +// 显示单个模型项 +function displayModelItem(modelName) { + const modelsList = document.getElementById('imported-models'); + if ([...modelsList.children].some(item => item.dataset.modelName === modelName)) { + return; + } + const modelItem = document.createElement('div'); + modelItem.className = 'model-item'; + modelItem.dataset.modelName = modelName; + modelItem.innerHTML = ` +
+ ${modelName} + +
+ `; + + // 绑定删除事件 + modelItem.querySelector('.delete-model').addEventListener('click', () => { + deleteModel(modelName); + }); + + modelsList.appendChild(modelItem); +} + +// 清空所有模型 +async function clearAllModels() { + try { + const modelInfos = await tf.io.listModels(); + const deletePromises = Object.keys(modelInfos) + .map(path => path.replace('indexeddb://', '')) + .map(modelName => tf.io.removeModel(`indexeddb://${modelName}`)); + + await Promise.all(deletePromises); + document.getElementById('imported-models').innerHTML = ''; + } catch (error) { + console.error('清空模型失败:', error); + alert('清空模型失败: ' + error.message); + } +} + +// 加载并显示所有模型 +async function loadAndDisplayAllModels() { + try { + const modelInfos = await tf.io.listModels(); + document.getElementById('imported-models').innerHTML = ''; + for (const [path] of Object.entries(modelInfos)) { + const modelName = path.replace('indexeddb://', ''); + displayModelItem(modelName); + } + } catch (error) { + console.error('加载模型列表失败:', error); + } +} + +async function createModal() { + const overlay = document.createElement('div'); + overlay.id = 'modalOverlay'; + Object.assign(overlay.style, { + display: 'none', + position: 'fixed', + top: '0', + left: '0', + width: '100%', + height: '100%', + backgroundColor: 'rgba(0,0,0,0.5)', + zIndex: '20011216', + pointerEvents: 'auto' + }); + const content = document.createElement('div'); + Object.assign(content.style, { + backgroundColor: 'white', + width: '60%', + maxHeight: '80%', + margin: '12vh auto', + padding: '20px 30px', + borderRadius: '12px' + }); + content.innerHTML = ` +

选择本地模型

+
+ + +
+ 导入模型名称: + + +
+
+
+ ❌ 模型结构描述文件(model.json) + +
未选择
+
+
+ ❌ 权重文件(model.weights.bin) +
0 个已选择
+
+
+
+
+
+

已导入模型

+
+ + +
+
+
+
加载中...
+
+
+
+ +
+
+ `; + overlay.appendChild(content); + document.body.appendChild(overlay); + + content.querySelector('.close-btn').addEventListener('click', closeModal); + overlay.addEventListener('click', (e) => { + if (e.target === overlay) closeModal(); + }); + // 获取DOM元素 + const modelUpload = document.getElementById('model-upload'); + const modelHandle = document.getElementById('model-handle'); + const outputDiv = document.getElementById('output'); + + let jsonFile = null; + let weightFiles = []; + + modelUpload.addEventListener('change', async (event) => { + const files = event.target.files; + + // 获取状态元素 + const jsonStatus = document.getElementById('json-status'); + const weightsStatus = document.getElementById('weights-status'); + + // 重置状态显示(保持完整文件名描述) + jsonStatus.querySelector('span').textContent = '❌ 模型结构描述文件(model.json)'; + jsonStatus.querySelector('div').textContent = '未选择'; + weightsStatus.querySelector('span').textContent = '❌ 权重文件(model.weights.bin)'; + weightsStatus.querySelector('div').textContent = '0 个已选择'; + + // 分离 JSON 和权重文件 + weightFiles = []; + for (let i = 0; i < files.length; i++) { + if (files[i].name.endsWith('.json')) { + jsonFile = files[i]; + } else { + weightFiles.push(files[i]); + } + } + + if (!jsonFile) { + alert('未找到 model.json 文件'); + return; + } + + outputDiv.innerHTML = '正在处理上传的模型文件...'; + + if (jsonFile) { + jsonStatus.querySelector('span').textContent = '✅ 模型结构描述文件(model.json)'; + jsonStatus.querySelector('div').textContent = '已选择'; + const modelName = jsonFile.name.replace('.json', ''); + document.getElementById('model-name').value = modelName; + } + + if (weightFiles.length > 0) { + weightsStatus.querySelector('span').textContent = '✅ 权重文件(model.weights.bin)'; + weightsStatus.querySelector('div').textContent = `${weightFiles.length} 个已选择`; + } + }); + + modelHandle.addEventListener('click', async () => { + try { + const modelNameInput = document.getElementById('model-name'); + const modelName = modelNameInput.value || 'mixly-model'; + + const model = await tf.loadLayersModel( + tf.io.browserFiles([jsonFile, ...weightFiles]) + ); + await model.save(`indexeddb://${modelName}`); + loadAndDisplayAllModels(); + outputDiv.innerHTML = `模型已成功保存为 ${modelName}!`; + } catch (error) { + outputDiv.innerHTML = `保存模型出错: ${error.message}`; + console.error(error); + } + }) + + content.querySelector('#refresh-models').addEventListener('click', loadAndDisplayAllModels); + content.querySelector('#clear-models').addEventListener('click', async () => { + if (confirm('确定要删除所有模型吗?此操作不可恢复!')) { + await clearAllModels(); + } + }); +} + +createModal(); + +await loadAndDisplayAllModels(); + +function openModal() { + loadAndDisplayAllModels(); + document.getElementById('modalOverlay').style.display = 'block'; +} + +const workspace = Blockly.getMainWorkspace(); +workspace.registerButtonCallback('handleModels', function () { + openModal(); +}); + + +async function prepare_qmyixtxi(imgTensor) { + let net = null; + + if (window.featureExtractor) { + net = window.featureExtractor; + } else { + net = await tf.loadGraphModel("../common/media/tfmodel/model.json"); + window.featureExtractor = net; + } + const preprocessedImg = imgTensor + .resizeBilinear([224, 224]) + .toFloat() + .div(tf.scalar(127.5)) + .sub(tf.scalar(1)) + .expandDims(0); + + const features = featureExtractor.predict(preprocessedImg); + + let activation = features; + return activation; +} +window.prepare_qmyixtxi = prepare_qmyixtxi; diff --git a/boards/default_src/python_pyodide/others/teachableMachine/App.vue b/boards/default_src/python_pyodide/others/teachableMachine/App.vue index d5208002..2ae86a68 100644 --- a/boards/default_src/python_pyodide/others/teachableMachine/App.vue +++ b/boards/default_src/python_pyodide/others/teachableMachine/App.vue @@ -8,31 +8,6 @@ import teachableModel from './components/teachableModel.vue'; // import 'element-plus/theme-chalk/el-notification.css'; // import './styles/index.scss'; - -const userInfo = reactive({ - is_Login: false, - username: '', - password: '', -}) - -const apiurl = ref('http://127.0.0.1:5174') -provide('apiurl', apiurl.value) - -provide('userInfo', userInfo) -onMounted(() => { - // 获取当前的LS里的已经登录的信息 - if ( - localStorage.getItem('myAIplatformUsername') - && localStorage.getItem('myAIplatformPassword') - ) { - userInfo.is_Login = true - userInfo.username = localStorage.getItem('myAIplatformUsername') - userInfo.password = localStorage.getItem('myAIplatformPassword') - } - else { - userInfo.is_Login = false - } -})