// 模型加载相关的composable import { ref } from 'vue' import * as trainUtils from '../../utils/config/educateUtils' export function useModelLoader(project, trainingDataStore) { const splitData = ref([]) const currentModelIndex = ref(-1) const originalModelConfig = ref(null) const currentModelConfig = ref(null) // 加载模型配置(从缓存或文件) const loadModelConfig = async (configPath, models, index) => { try { // 先检查Store中是否有缓存的配置 if (trainingDataStore) { const cachedModel = trainingDataStore.models.find(m => m.configPath === configPath) if (cachedModel?.config) { if (models && models[index]) { models[index].config = cachedModel.config } console.log('[useModelLoader] 使用缓存的模型配置') return cachedModel.config } } // 如果没有缓存,从文件加载 if (window.electronAPI?.readFile) { const result = await window.electronAPI.readFile(configPath) if (result.success) { const config = JSON.parse(result.content) if (models && models[index]) { models[index].config = config } // 更新Store中的缓存 if (trainingDataStore) { trainingDataStore.addOrUpdateModel({ ...models[index], config }) } return config } } } catch (error) { console.error('[useModelLoader] 加载模型配置失败:', error) } return null } // 计算模型的拆分百分比(从Evaluate.vue页面复制的逻辑) const calculateModelPercentage = (model) => { if (!model.split?.statistics) return '0·0·0%' const stats = model.split.statistics const total = stats.totalImages || 1 const trainPercent = Math.round((stats.trainImages || 0) / total * 100) const valPercent = Math.round((stats.valImages || 0) / total * 100) const testPercent = Math.round((stats.testImages || 0) / total * 100) return `${trainPercent}·${valPercent}·${testPercent}%` } // 为评估页面增强模型数据(添加拆分统计信息) const enhanceModelsWithSplitStats = async (models, projectData) => { console.log('[useModelLoader] 开始增强模型数据,添加拆分统计信息') for (let i = 0; i < models.length; i++) { const model = models[i] const splitId = model.config?.split?.id || model.config?.split_id if (splitId && (!model.split || !model.split.statistics)) { try { const stats = await trainUtils.calculateSplitStats(projectData, splitId) if (stats) { // 如果模型已有split对象但没有statistics,更新它 if (model.split) { model.split.statistics = stats } else { model.split = { id: splitId, name: model.config?.split?.name || splitId, statistics: stats } } console.log(`[useModelLoader] 模型 ${model.title} 拆分统计加载成功`) } } catch (error) { console.error(`[useModelLoader] 模型 ${model.title} 拆分统计加载失败:`, error) } } } console.log('[useModelLoader] 模型数据增强完成') } // 加载已训练完成的模型(从 models 目录加载) const loadCompletedModels = async (autoSelectFirst = false) => { if (!project.value) { console.warn('[useModelLoader] 项目信息不存在,跳过模型加载') return } try { console.log('[useModelLoader] 开始加载已完成的模型...') const modelsDir = `${project.value.path}/models` let completedModels = [] // 从 models 目录读取配置文件 if (window.electronAPI && window.electronAPI.readDirectory) { const dirExists = await window.electronAPI.pathExists(modelsDir) if (!dirExists) { console.log('[useModelLoader] 模型目录不存在') splitData.value = [] return } const readResult = await window.electronAPI.readDirectory(modelsDir) if (!readResult.success) { throw new Error(`读取模型目录失败: ${readResult.error}`) } const modelFiles = readResult.files.filter(file => file.endsWith('.json')) // 读取并解析每个模型配置文件 for (const fileName of modelFiles) { try { const filePath = `${modelsDir}/${fileName}` const readFileResult = await window.electronAPI.readFile(filePath) if (!readFileResult.success) continue const modelConfig = JSON.parse(readFileResult.content) // 只添加已完成训练的模型 if (modelConfig.status === 'completed' || modelConfig.status === 'trained') { // 保存模型配置路径 modelConfig._modelConfigPath = filePath completedModels.push(modelConfig) } } catch (error) { console.error(`[useModelLoader] 解析模型配置文件失败 ${fileName}:`, error) } } } // 转换为显示格式 const displayModels = await formatModelsForDisplay(completedModels, project.value) if (displayModels.length === 0) { splitData.value = [] } else { // 按创建时间排序 displayModels.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt)) splitData.value = displayModels // 只在需要时自动选择第一个模型 if (autoSelectFirst && displayModels.length > 0) { await selectModelForEvaluation(0) } } console.log('[useModelLoader] 加载完成,共', displayModels.length, '个已训练模型') } catch (error) { console.error('[useModelLoader] 加载模型失败:', error) splitData.value = [] } } // 格式化模型数据用于显示(从Evaluate.vue页面复制的逻辑) const formatModelsForDisplay = async (models, projectData) => { const displayModels = [] for (let i = 0; i < models.length; i++) { const model = models[i] const modelType = model.model_type || model.neuralNetwork?.name || 'default' // 使用模型配置路径 const filePath = model._modelConfigPath || model.configPath || `${projectData.path}/models/configs/${model.id || model.name}.json` // 读取拆分数据以获取统计信息 let splitStatistics = null if (model.split?.id) { // 直接使用原始拆分数据(不再创建评估专用拆分) const splitPath = `${projectData.path}/splits/${model.split.id}/metadata.json` if (window.electronAPI && window.electronAPI.pathExists) { const pathExists = await window.electronAPI.pathExists(splitPath) if (pathExists) { // 读取拆分数据文件 const splitResult = await window.electronAPI.readFile(splitPath) if (splitResult.success) { try { const splitData = JSON.parse(splitResult.content) splitStatistics = calculateSplitStatistics(splitData, model.split.id) } catch (error) { console.error('[useModelLoader] 解析拆分数据失败:', error) } } } } } // 合并拆分统计信息到模型数据 const enhancedSplit = { ...model.split, statistics: splitStatistics } displayModels.push({ id: model.id, title: model.name || model.id, model_type: modelType, percentage: calculateModelPercentage({ ...model, split: enhancedSplit }), images: `${splitStatistics?.totalImages || 0}个图像(100%)`, instances: `${splitStatistics?.totalInstances || 0}个实例(100%)`, status: getModelStatusText(model.status), evaluation: model.evaluation || model.results?.evaluation || '未评估', comment: model.comment || '', config: model, configPath: filePath, assessState: model.assessState || 0, evaluationTaskId: model.evaluationTaskId, createdAt: model.createdAt, split: enhancedSplit }) } return displayModels } // 计算拆分数据的统计信息(从Evaluate.vue页面复制的逻辑) const calculateSplitStatistics = (splitData, splitId) => { if (!splitData || !splitData.splits || !splitData.splits[splitId]) { return null } const split = splitData.splits[splitId] const images = splitData.images || {} // 计算各阶段的图像数量 const trainImages = split.train?.length || 0 const valImages = split.val?.length || 0 const testImages = split.test?.length || 0 const unlabeledImages = split.unlabeled?.length || 0 const unselectedImages = split.unselected?.length || 0 // 计算evaluation阶段的图像(所有标注但未被选入train/val/test的图像) let evaluationImages = 0 Object.keys(images).forEach(imageId => { const image = images[imageId] // 如果图像有标注但不在train/val/test/unlabeled中 if (image.annotations && image.annotations.length > 0) { const isInSplit = (split.train && split.train.includes(imageId)) || (split.val && split.val.includes(imageId)) || (split.test && split.test.includes(imageId)) || (split.unlabeled && split.unlabeled.includes(imageId)) if (!isInSplit) { evaluationImages++ } } }) // 计算总图像数和总实例数 const totalImages = Object.keys(images).length let totalInstances = 0 Object.values(images).forEach(image => { if (image.annotations) { totalInstances += image.annotations.length } }) return { trainImages, valImages, testImages, unlabeledImages, evaluationImages, unselectedImages, totalImages, totalInstances } } // 获取模型状态文本 const getModelStatusText = (status) => { switch (status) { case 'completed': case 'trained': return '已完成训练' case 'training': return '训练中' case 'failed': return '训练失败' case 'created': return '开始训练' default: return '未知状态' } } // 选择模型进行评估 const selectModelForEvaluation = async (index) => { currentModelIndex.value = index const model = splitData.value[index] if (!model) { console.warn('[useModelLoader] 模型不存在') return null } try { // 加载模型配置文件 if (model.configPath) { // 使用模型配置路径 if (window.electronAPI && window.electronAPI.readFile) { const readResult = await window.electronAPI.readFile(model.configPath) if (readResult.success) { const config = JSON.parse(readResult.content) originalModelConfig.value = config currentModelConfig.value = JSON.parse(JSON.stringify(config)) console.log('[useModelLoader] 加载模型配置成功:', model.title, model.configPath) return currentModelConfig.value } } } else if (model.config && model.config.id && project.value) { // 尝试构建模型配置路径 const modelConfigPath = `${project.value.path}/models/configs/${model.config.id}.json` if (window.electronAPI && window.electronAPI.pathExists) { const modelExists = await window.electronAPI.pathExists(modelConfigPath) if (modelExists && window.electronAPI.readFile) { const readResult = await window.electronAPI.readFile(modelConfigPath) if (readResult.success) { const config = JSON.parse(readResult.content) originalModelConfig.value = config currentModelConfig.value = JSON.parse(JSON.stringify(config)) // 更新模型的配置路径 model.configPath = modelConfigPath console.log('[useModelLoader] 加载模型配置成功:', model.title, modelConfigPath) return currentModelConfig.value } } else { console.warn('[useModelLoader] 模型配置文件不存在:', modelConfigPath) } } } else { console.warn('[useModelLoader] 无法确定模型配置路径') } } catch (error) { console.error('[useModelLoader] 读取评估配置失败:', error) } return null } // 处理模型数据以供评估页面使用(从Evaluate.vue页面提取的逻辑) const processModelsForEvaluation = (completedModels) => { return completedModels.map((model, index) => { console.log(`[useModelLoader] 处理模型 ${index}:`, model.title || model.id) console.log('- 原始拆分统计:', model.split?.statistics) // 从模型配置中获取拆分ID const splitId = model.config?.split?.id || model.config?.split_id console.log('- 配置中的拆分ID:', splitId) // 获取拆分信息(应该在onMounted时已经加载) let splitInfo = model.split let splitStatistics = model.split?.statistics if (!splitInfo && splitId) { console.log('- 警告:拆分信息未加载,模型:', model.title, '拆分ID:', splitId) } // 处理模型数据,添加评估需要的字段 const evalModel = { ...model, split: splitInfo, // 确保拆分信息被传递 images: `${splitStatistics?.totalImages || 0}个图像(100%)`, instances: `${splitStatistics?.totalInstances || 0}个实例(100%)`, percentage: calculateModelPercentage({ ...model, split: splitInfo }), assessState: model.assessState || 0, evaluation: model.evaluation || '' } console.log('- 处理后:', { images: evalModel.images, instances: evalModel.instances, percentage: evalModel.percentage, splitInfo: evalModel.split }) return evalModel }) } return { splitData, currentModelIndex, originalModelConfig, currentModelConfig, loadCompletedModels, selectModelForEvaluation, loadModelConfig, enhanceModelsWithSplitStats, calculateModelPercentage, processModelsForEvaluation } }