综合训练(多物种合并模型)全链路:合并打包/独立版本序列/App 覆盖互斥 + 管理端发布入口

- 后端:/admin/trainings/combined 发起(≥2 数据集、类别重映射、防重名、负样本单份);
  model_training/model_version 加 kind+dataset_ids(迁移 v14),综合任务 dataset_id=0、
  文件基名 combined(_n)、版本序列独立;训练列表补 published 标记
- 管理端:数据训练页工具栏发起综合训练;横幅常驻进行中任务 + 每档最近一条已结束任务,
  成功未发布给「发布模型」入口(可关闭收起)
- App:目录解析 kind/datasetIds、激活覆盖互斥、自动更新退场改目标档待办横幅手动一键下载
This commit is contained in:
2026-09-09 15:45:51 +08:00
parent d8853ee2ab
commit 87ab06221c
17 changed files with 796 additions and 104 deletions
@@ -18,8 +18,9 @@ String _variantLabel(String v) => v == kVariantN ? '高性能' : '高精度';
/// - 任一档失败 → 错误提示 + 重试(只补下未成功的档);
/// - 目标档已激活 →「使用中」点击取消使用;
/// - 目标档已下载未激活 →「使用」直接启用(另一档在运行会被自动停用);
/// - 目标档未下载 →「下载」取回缺失档(目标档落地自动启用、伴档备好
/// 另一档在使用时补下目标档后自动切换过去
/// - 目标档未下载 →「下载」取回目标档(2026-09-09 双档分别下载,伴档不随下)
/// 另一档在使用时补下目标档后自动切换过去。切档/刷新检测目标档「未下载/
/// 待更新」出横幅,用户手动一键下载/更新(自动更新已退场)。
class ModelCatalogSection extends StatelessWidget {
final ModelManager manager;
@@ -61,6 +62,50 @@ class ModelCatalogSection extends StatelessWidget {
'卡片操作面向所选档位;同一动物一次只运行一档,切换会自动停用另一档',
style: TextStyle(color: Colors.white38, fontSize: 11),
),
// 目标档待办横幅(切档检测):未下载/版本落后的模型,用户手动一键触发
if (manager.modePending.isNotEmpty) ...[
const SizedBox(height: 8),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 10, vertical: 6),
decoration: BoxDecoration(
color: Colors.orange.withValues(alpha: 0.15),
borderRadius: BorderRadius.circular(8),
),
child: Row(
children: [
const Icon(Icons.system_update_alt,
color: Colors.orangeAccent, size: 15),
const SizedBox(width: 6),
Expanded(
child: Text(
'${_variantLabel(manager.mode)}模式:'
'${manager.modePending.length} 个模型待下载/更新',
style: const TextStyle(
color: Colors.orangeAccent, fontSize: 11.5),
),
),
TextButton.icon(
onPressed: () {
for (final i in manager.modePending.toList()) {
manager.downloadModel(i);
}
},
icon: const Icon(Icons.download, size: 14),
label: const Text('一键下载',
style: TextStyle(fontSize: 11.5)),
style: TextButton.styleFrom(
foregroundColor: Colors.orangeAccent,
visualDensity: VisualDensity.compact,
padding: const EdgeInsets.symmetric(horizontal: 6),
minimumSize: const Size(0, 26),
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
),
],
),
),
],
const SizedBox(height: 12),
Row(
children: [
@@ -414,24 +459,21 @@ class _SpeciesCard extends StatelessWidget {
),
);
} else {
// 目标档未下载:下载全部缺失档(目标档落地自动启用、伴档备好待切);
// 另一档在使用时按钮同样为「下载」:补下目标档后自动切换(停用另一档)
// 目标档未下载:下载目标档(2026-09-09 双档分别下载——伴档不随下,
// 缺失/待更新由切档横幅提示手动补);另一档在使用时补下目标档后自动切换
final use = activeOther.isNotEmpty;
mainBtn = SizedBox(
height: 30,
child: FilledButton(
onPressed: () async {
if (!use) {
for (final i in items) {
if (!manager.isDownloaded(i.datasetId, i.variant)) {
manager.downloadModel(i);
}
}
return;
}
if (!manager.isDownloaded(target.datasetId, target.variant)) {
final ok = await manager.downloadModel(target);
if (!ok) return; // 下载失败/取消:错误分支展示,保持现状
if (use) {
final ok = await manager.downloadModel(target);
if (!ok) return; // 下载失败/取消:错误分支展示,保持现状
} else {
manager.downloadModel(target);
return;
}
}
await manager.setActive(target.datasetId, target.variant, true);
},