Files
mixly3/boards/default_src/python_pyodide/others/loader.js
2025-11-06 18:43:47 +08:00

330 lines
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import NavExt from './nav-ext';
import * as tf from '@tensorflow/tfjs';
import './tensorflow';
import * as Blockly from 'blockly/core';
NavExt.init();
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 = `
<div style="display: flex; justify-content: space-between; align-items: center; padding: 8px 10px; border-bottom: 1px solid #eee;">
<span style="font-size: 1em; color: #333;">${modelName}</span>
<button class="delete-model" style="
background: #f44336;
color: white;
border: none;
padding: 4px 10px;
border-radius: 4px;
cursor: pointer;
font-size: 0.9em;
transition: background-color 0.2s;
">删除</button>
</div>
`;
// 绑定删除事件
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 = `
<h2 style="margin-bottom: 20px;">选择本地模型</h2>
<div style="margin-bottom: 25px; position: relative; min-height: 200px;"> <!-- 新增 min-height 和 position -->
<input type="file" id="model-upload" accept=".json,.bin" multiple style="display: none;">
<label for="model-upload" style="
background: #1216ab;
color: white;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
">选择模型文件</label>
<div style="display: flex; gap: 10px; margin-top: 15px; align-items: center;">
<span style="line-height: 36px;">导入模型名称:</span>
<input type="text" id="model-name" placeholder="模型名称" value="my-model" style="
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
flex: 1;
max-width: 200px;
">
<button id="model-handle" style="
background: #1216ab;
color: white;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
">保存模型</button>
</div>
<div style="margin-top: 15px; display: flex; gap: 20px;">
<div id="json-status" style="
background: #f5f5f5;
padding: 10px;
border-radius: 4px;
flex: 1;
">
<span>❌ 模型结构描述文件model.json</span>
<div style="color: #666; font-size: 0.9em; margin-top: 5px;">未选择</div>
</div>
<div id="weights-status" style="
background: #f5f5f5;
padding: 10px;
border-radius: 4px;
flex: 1;
">
<span>❌ 权重文件model.weights.bin</span>
<div style="color: #666; font-size: 0.9em; margin-top: 5px;">0 个已选择</div>
</div>
</div>
<div id="output" style="margin-bottom: 15px; min-height: 40px;"></div>
<div style="margin-top: 20px;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
<h3>已导入模型</h3>
<div style="display: flex; gap: 10px;">
<button id="refresh-models" style="
background: #4CAF50;
color: white;
padding: 5px 10px;
border-radius: 4px;
cursor: pointer;
border: none;
">刷新</button>
<button id="clear-models" style="
background: #f44336;
color: white;
padding: 5px 10px;
border-radius: 4px;
cursor: pointer;
border: none;
">清空</button>
</div>
</div>
<div id="imported-models" style="
background: #f5f5f5;
padding: 10px;
border-radius: 4px;
height: 200px;
overflow-y: auto;
">
<div style="color: #666; font-style: italic;">加载中...</div>
</div>
</div>
<div style="display: flex; justify-content: flex-end; margin-top: 15px;">
<button class="close-btn" style="
background: #e0e0e0;
border: none;
padding: 8px 20px;
border-radius: 4px;
cursor: pointer;
">关闭</button>
</div>
</div>
`;
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 = window.featureExtractor.predict(preprocessedImg);
let activation = features;
return activation;
}
window.prepare_qmyixtxi = prepare_qmyixtxi;