package dto import ( "github.com/gogf/gf/v2/frame/g" "github.com/gogf/gf/v2/net/ghttp" "github.com/gogf/gf/v2/os/gtime" ) // 模型训练体系接口(数据集 → 标注 → 训练 → 模型版本),组前缀 /api/v1/admin。 // 图片/标注/模型文件均在服务器磁盘(app.datasetDir),DB 只存元数据。 // 图片访问为直接写响应体场景,由 controller 以 *ghttp.Request 方法实现 // (见 controller/admin.go 的 Image 方法)。 // ---------- 数据集管理 ---------- // AdminDatasetListReq 数据集列表(创建时间倒序) type AdminDatasetListReq struct { g.Meta `path:"/datasets" method:"get" summary:"数据集列表" tags:"管理端"` Keyword string `json:"keyword" v:"length:0,50" dc:"名称模糊匹配"` Page int `json:"page" v:"integer|min:1" dc:"页码,默认 1"` Size int `json:"size" v:"integer|min:1|max:100" dc:"每页条数,默认 20"` } // AdminDatasetItem 数据集条目(卡片展示用;AI 端点/训练机 SSH 走 config.yml 全局配置(localAi / training.ssh)) type AdminDatasetItem struct { Id int64 `json:"id"` Name string `json:"name"` Source string `json:"source"` // manual | ai ImageCount int64 `json:"imageCount"` LabeledCount int64 `json:"labeledCount"` Status string `json:"status"` // building | labeled | synced Cover string `json:"cover"` // 封面文件名 Description string `json:"description"` // 描述 NamePrefix string `json:"namePrefix"` // AI 生成图文件名前缀 GenSpecies string `json:"genSpecies"` // 生成参数池:物种(单值) GenTone string `json:"genTone"` // 轮廓色词(单值) GenHeights float64 `json:"genHeights"` // 站高cm(数值) GenScenes string `json:"genScenes"` // 场景池(JSON 数组) GenActions string `json:"genActions"` // 动作池(JSON 数组) GenOcclusions string `json:"genOcclusions"` // 遮挡池(JSON 数组) GenClasses string `json:"genClasses"` // 第二标注类别名(单值) SortOrder int64 `json:"sortOrder"` // 序号(列表排序主键,升序) TrainingId int64 `json:"trainingId"` // 最新训练记录 id(发布/详情用) TrainingStatus string `json:"trainingStatus"` // 最新训练记录状态 running|success|failed|空 TrainingError string `json:"trainingError"` // 最新训练记录失败原因(failed 时展示用) TrainingPublished bool `json:"trainingPublished"` // 该训练是否已发布过版本(已发布不再显示发布按钮) TrainingCurrentEpoch int `json:"trainingCurrentEpoch"` TrainingTotalEpochs int `json:"trainingTotalEpochs"` CreatedAt *gtime.Time `json:"createdAt"` UpdatedAt *gtime.Time `json:"updatedAt"` } type AdminDatasetListRes struct { Total int64 `json:"total"` List []*AdminDatasetItem `json:"list"` } // AdminDatasetUpdateReq 更新数据集配置(空值字段不覆盖原值,保留现有配置;改名时同步迁移 // 图片目录与模型文件,名称唯一且同是磁盘目录名; // AI 端点/训练机 SSH 走 config.yml 全局配置(localAi / training.ssh)。 // gen_* 7 列(species/tone/heights/scenes/actions/occlusions/classes)不在此维护—— // 创建数据集时 VLM 生成,重新生成走 gen-pools 接口 type AdminDatasetUpdateReq struct { g.Meta `path:"/datasets/update" method:"post" summary:"更新数据集配置" tags:"管理端"` Id int64 `json:"id" v:"required|min:1" dc:"数据集 id"` Name string `json:"name" v:"length:0,50" dc:"数据集名称(非空且变化才更新;同是目录名)"` NamePrefix string `json:"namePrefix" v:"length:0,50" dc:"AI 生成图文件名前缀(非空才更新)"` Description string `json:"description" v:"length:0,500" dc:"描述(非空才更新)"` Cover string `json:"cover" v:"length:0,200" dc:"封面文件名(非空才更新)"` SortOrder *int64 `json:"sortOrder" dc:"序号(升序排列,缺省不更新)"` } type AdminDatasetUpdateRes struct { Id int64 `json:"id"` } // AdminDatasetCreateReq 新建数据集(名称同时是磁盘目录名:中文/字母/数字/下划线/短横线,唯一; // 单物种规则:数据集名即物种,VLM 按数据集名自动生成生成参数池,表单无动物名称输入) type AdminDatasetCreateReq struct { g.Meta `path:"/datasets" method:"post" summary:"新建数据集" tags:"管理端"` Name string `json:"name" v:"required|regex:^[a-zA-Z0-9_一-龥-]+$|length:1,50" dc:"数据集名称(同是目录名,唯一)"` Source string `json:"source" v:"required|in:manual,ai" dc:"图片来源 manual|ai"` NamePrefix string `json:"namePrefix" v:"length:0,50" dc:"AI 生成图文件名前缀(可选,字母/数字/下划线/短横线)"` Cover string `json:"cover" v:"length:0,200" dc:"封面文件名(新建对话框预生成封面后回传;非空则跳过自动生成)"` SortOrder *int64 `json:"sortOrder" dc:"序号(列表排序主键,升序;缺省 0)"` } type AdminDatasetCreateRes struct { Id int64 `json:"id"` PoolsGenerated bool `json:"poolsGenerated"` // 是否已用 VLM 生成生成参数池 PoolError string `json:"poolError"` // 参数池生成失败原因(可用 gen-pools 补生成) CoverGenerated bool `json:"coverGenerated"` // 是否已生成封面(16:9、1雄1雌) CoverError string `json:"coverError"` // 封面生成失败原因(可编辑模式重新生成) } // AdminGenPoolsReq 重新生成生成参数池(VLM 覆盖写;编辑对话框「VLM 重新生成参数」入口, // 按数据集名生成——单物种规则,无动物名称输入) type AdminGenPoolsReq struct { g.Meta `path:"/datasets/gen-pools" method:"post" summary:"重新生成生成参数池" tags:"管理端"` DatasetId int64 `json:"datasetId" v:"required|min:1" dc:"数据集 id"` } type AdminGenPoolsRes struct { GenSpecies string `json:"genSpecies"` GenTone string `json:"genTone"` GenHeights float64 `json:"genHeights"` GenScenes string `json:"genScenes"` GenActions string `json:"genActions"` GenOcclusions string `json:"genOcclusions"` GenClasses string `json:"genClasses"` } // AdminDatasetDeleteReq 删除数据集(删除图片文件+标注目录+记录;AI 生成图为付费资产,前端须带确认文案) type AdminDatasetDeleteReq struct { g.Meta `path:"/datasets/delete" method:"post" summary:"删除数据集" tags:"管理端"` Id int64 `json:"id" v:"required|min:1" dc:"数据集 id"` } type AdminDatasetDeleteRes struct{} // AdminDatasetUploadReq 上传图片(multipart 多文件;重名跳过并计数) type AdminDatasetUploadReq struct { g.Meta `path:"/datasets/upload" method:"post" summary:"上传图片" tags:"管理端" mime:"multipart/form-data"` DatasetId int64 `json:"datasetId" v:"required|min:1" dc:"数据集 id"` Files []*ghttp.UploadFile `json:"files" dc:"图片文件(jpg/jpeg/png,多选)"` } type AdminDatasetUploadRes struct { Added int `json:"added"` Skipped int `json:"skipped"` // 重名跳过数 } // AdminDatasetGenerateReq AI 生成图片(异步任务;prompt 可手填覆盖模板,禁止含目标位置描述; // 未手填走 imageGen.promptTemplate 组装(单物种规则:物种固定取数据集 gen_species[0]/数据集名, // 表单无物种输入),scene/action/occlusion 从数据集表池随机。 // animalCount 仅作为标注框数上限存储(自动标注按置信度裁剪 ≤N),不参与生成校验; // 距离校验已取消(2026-08-28)。文件名前缀为数据集属性(datasets 表 name_prefix)。 // 生成耗时约 40s/张(localai),批量请用返回的 TaskId 轮询 /datasets/gen-task 进度。 type AdminDatasetGenerateReq struct { g.Meta `path:"/datasets/generate" method:"post" summary:"AI 生成图片(异步任务)" tags:"管理端"` DatasetId int64 `json:"datasetId" v:"required|min:1" dc:"数据集 id"` Prompt string `json:"prompt" v:"length:0,500" dc:"完整提示词(可选,填写则覆盖模板;禁止含位置描述)"` Count int `json:"count" v:"required|integer|min:1|max:1000" dc:"生成张数 1-1000"` Size string `json:"size" v:"required|in:704x1248" dc:"图片尺寸"` Distance int `json:"distance" v:"required|min:1" dc:"目标距离(米,固定值,注入提示词)"` } type AdminDatasetGenerateRes struct { TaskId int64 `json:"taskId"` // 生成任务 id(前端轮询 /datasets/gen-task 进度) Total int `json:"total"` // 计划生成张数 } // AdminGenTaskQueryReq 生成任务进度查询(前端轮询;任务完成后错误信息在 Error) type AdminGenTaskQueryReq struct { g.Meta `path:"/datasets/gen-task" method:"get" summary:"生成任务进度" tags:"管理端"` DatasetId int64 `json:"datasetId" v:"required|min:1" dc:"数据集 id"` } type AdminGenTaskQueryRes struct { Id int64 `json:"id"` DatasetId int64 `json:"datasetId"` Status string `json:"status"` // running | done | failed Total int `json:"total"` Done int `json:"done"` Error string `json:"error"` CreatedAt *gtime.Time `json:"createdAt"` FinishedAt *gtime.Time `json:"finishedAt"` } // AdminDatasetImagesReq 数据集图片列表(标注工作台/网格预览用) type AdminDatasetImagesReq struct { g.Meta `path:"/datasets/images" method:"get" summary:"数据集图片列表" tags:"管理端"` DatasetId int64 `json:"datasetId" v:"required|min:1" dc:"数据集 id"` } // AdminImageItem 图片条目(url 为管理端图片访问地址,前端拼 apiBaseUrl 使用) type AdminImageItem struct { Id int64 `json:"id"` Filename string `json:"filename"` Source string `json:"source"` // manual | ai Prompt string `json:"prompt"` Url string `json:"url"` CreatedAt *gtime.Time `json:"createdAt"` } type AdminDatasetImagesRes struct { List []*AdminImageItem `json:"list"` } // AdminDatasetImagesDeleteReq 删除图片(删文件+删记录;付费资产,前端须带确认文案) type AdminDatasetImagesDeleteReq struct { g.Meta `path:"/datasets/images/delete" method:"post" summary:"删除图片" tags:"管理端"` DatasetId int64 `json:"datasetId" v:"required|min:1" dc:"数据集 id"` Ids []int64 `json:"ids" v:"required|min-length:1" dc:"图片记录 id 列表"` } type AdminDatasetImagesDeleteRes struct{} // AdminDatasetImageReq 图片访问(直写响应体,例外场景:controller 经 service 校验归属后输出二进制) type AdminDatasetImageReq struct { g.Meta `path:"/datasets/image" method:"get" summary:"图片访问" tags:"管理端"` DatasetId int64 `json:"datasetId" v:"required|min:1" dc:"数据集 id"` Filename string `json:"filename" v:"required" dc:"图片文件名"` } type AdminDatasetImageRes struct{} // AdminDatasetCoverUploadReq 上传数据集封面(multipart 单文件;覆盖旧封面,UUID 命名 jpg) type AdminDatasetCoverUploadReq struct { g.Meta `path:"/datasets/cover" method:"post" summary:"上传数据集封面" tags:"管理端" mime:"multipart/form-data"` DatasetId int64 `json:"datasetId" v:"required|min:1" dc:"数据集 id"` File *ghttp.UploadFile `json:"file" dc:"封面图片(jpg/jpeg/png,≤2MB)"` } type AdminDatasetCoverUploadRes struct{} // AdminDatasetCoverReq 封面访问(直写响应体,与 Image 同模式; // datasetId=0 时按 name+filename 直读——新建对话框预生成封面回显用) type AdminDatasetCoverReq struct { g.Meta `path:"/datasets/cover" method:"get" summary:"数据集封面访问" tags:"管理端"` DatasetId int64 `json:"datasetId" v:"min:0" dc:"数据集 id"` Name string `json:"name" v:"length:0,50" dc:"数据集名称(datasetId=0 时必填)"` Filename string `json:"filename" v:"length:0,200" dc:"封面文件名(datasetId=0 时必填)"` } type AdminDatasetCoverRes struct{} // AdminDatasetCoverDeleteReq 删除数据集封面(删文件+清字段;列表卡片恢复占位图) type AdminDatasetCoverDeleteReq struct { g.Meta `path:"/datasets/cover/delete" method:"post" summary:"删除数据集封面" tags:"管理端"` DatasetId int64 `json:"datasetId" v:"required|min:1" dc:"数据集 id"` } type AdminDatasetCoverDeleteRes struct{} // AdminGenCoverReq 生成数据集封面(z-image 文生图:16:9、1 雄 1 雌;覆盖旧封面;编辑对话框按钮入口)。 // datasetId=0 时按 name 预生成(新建对话框——数据集未创建,封面仅落盘不写库, // 创建请求带 cover 回传写库),物种取数据集名。 type AdminGenCoverReq struct { g.Meta `path:"/datasets/cover/generate" method:"post" summary:"生成数据集封面" tags:"管理端"` DatasetId int64 `json:"datasetId" v:"min:0" dc:"数据集 id(0=新建预生成)"` Name string `json:"name" v:"length:0,50" dc:"数据集名称(datasetId=0 时必填)"` } type AdminGenCoverRes struct { Cover string `json:"cover" dc:"新封面文件名"` } // ---------- 训练编排 ---------- // AdminTrainingListReq 训练任务列表(创建时间倒序) type AdminTrainingListReq struct { g.Meta `path:"/trainings" method:"get" summary:"训练任务列表" tags:"管理端"` Status string `json:"status" v:"in:running,success,failed" dc:"状态筛选"` Page int `json:"page" v:"integer|min:1" dc:"页码,默认 1"` Size int `json:"size" v:"integer|min:1|max:100" dc:"每页条数,默认 20"` } // AdminTrainingItem 训练任务条目(列表不含日志尾部,详情接口返回) type AdminTrainingItem struct { Id int64 `json:"id"` Name string `json:"name"` DatasetId int64 `json:"datasetId"` DatasetName string `json:"datasetName"` Status string `json:"status"` // running | success | failed Imgsz int `json:"imgsz"` Epochs int `json:"epochs"` Batch int `json:"batch"` Device string `json:"device"` CurrentEpoch int `json:"currentEpoch"` TotalEpochs int `json:"totalEpochs"` Metrics string `json:"metrics"` Error string `json:"error"` StartedAt *gtime.Time `json:"startedAt"` FinishedAt *gtime.Time `json:"finishedAt"` CreatedAt *gtime.Time `json:"createdAt"` } type AdminTrainingListRes struct { Total int64 `json:"total"` List []*AdminTrainingItem `json:"list"` } // AdminTrainingStartReq 发起训练(并发度 1:已有 running 任务时拒绝;训练参数走 config.yml training 节点,name 空则自动生成) type AdminTrainingStartReq struct { g.Meta `path:"/trainings" method:"post" summary:"发起训练" tags:"管理端"` DatasetId int64 `json:"datasetId" v:"required|min:1" dc:"数据集 id"` Name string `json:"name" v:"length:0,50" dc:"任务名称,空自动生成"` } type AdminTrainingStartRes struct { Id int64 `json:"id"` } // AdminTrainingDetailReq 训练任务详情(含日志尾部) type AdminTrainingDetailReq struct { g.Meta `path:"/trainings/detail" method:"get" summary:"训练任务详情" tags:"管理端"` Id int64 `json:"id" v:"required|min:1" dc:"训练任务 id"` } // AdminTrainingDetailRes 详情 = 条目 + 日志尾部 type AdminTrainingDetailRes struct { AdminTrainingItem LogTail string `json:"logTail"` } // AdminTrainingCancelReq 取消训练(杀进程,任务置 failed) type AdminTrainingCancelReq struct { g.Meta `path:"/trainings/cancel" method:"post" summary:"取消训练" tags:"管理端"` Id int64 `json:"id" v:"required|min:1" dc:"训练任务 id"` } type AdminTrainingCancelRes struct{} // AdminTrainingPublishReq 发布模型版本(仅 success 任务;按数据集版本号 m.. 自增) type AdminTrainingPublishReq struct { g.Meta `path:"/trainings/publish" method:"post" summary:"发布模型版本" tags:"管理端"` Id int64 `json:"id" v:"required|min:1" dc:"训练任务 id"` } type AdminTrainingPublishRes struct { Version string `json:"version"` } // ---------- 模型版本 ---------- // ---------- 预标注 ---------- // AdminLabelTaskListReq 预标注任务列表(创建时间倒序) type AdminLabelTaskListReq struct { g.Meta `path:"/label-tasks" method:"get" summary:"预标注任务列表" tags:"管理端"` Page int `json:"page" v:"integer|min:1" dc:"页码,默认 1"` Size int `json:"size" v:"integer|min:1|max:100" dc:"每页条数,默认 20"` } type AdminLabelTaskItem struct { Id int64 `json:"id"` DatasetId int64 `json:"datasetId"` DatasetName string `json:"datasetName"` Status string `json:"status"` // running | done Total int `json:"total"` Done int `json:"done"` Error string `json:"error"` CreatedAt *gtime.Time `json:"createdAt"` FinishedAt *gtime.Time `json:"finishedAt"` } type AdminLabelTaskListRes struct { Total int64 `json:"total"` List []*AdminLabelTaskItem `json:"list"` } // AdminLabelTaskStartReq 发起预标注(RF-DETR 扫描,候选框合并入 datasets//boxes.json) type AdminLabelTaskStartReq struct { g.Meta `path:"/label-tasks" method:"post" summary:"发起预标注" tags:"管理端"` DatasetId int64 `json:"datasetId" v:"required|min:1" dc:"数据集 id"` Filenames []string `json:"filenames" v:"length:0,500" dc:"选中图片列表(多选批量);缺省=全量扫描"` } type AdminLabelTaskStartRes struct { Id int64 `json:"id"` } // AdminImageVlmReviewReq 单图 VLM 补检:qwen3.6-35b-a3b 按「环境/光线/习性」推理 RF-DETR 之外 // 可能藏匿目标的位置(排除已确认坐标),最多补 3 个疑似框(class 1)追加到 labels_json。 // 依赖 localAi.vlmModel(默认 qwen3.6-35b-a3b);VL 与 z-image 生成不能同时驻留显存,批量补检 // 应在生成队列空闲时执行 type AdminImageVlmReviewReq struct { g.Meta `path:"/datasets/images/vlm-review" method:"post" summary:"VLM 藏匿位补检" tags:"管理端"` DatasetId int64 `json:"datasetId" v:"required|min:1" dc:"数据集 id"` ImageId int64 `json:"imageId" v:"required|min:1" dc:"图片 id"` } type AdminImageVlmReviewRes struct { Added int `json:"added"` // 新增疑似框数(≤3) Boxes []*AdminLabelBox `json:"boxes"` // VLM 新增框(归一化坐标,class=1) Note string `json:"note"` // 未补检原因(如已确认框为空/VL 无输出) } // AdminLabelTaskDetailReq 预标注任务详情(每张图候选框 + 已确认标注,供工作台 canvas 叠框) type AdminLabelTaskDetailReq struct { g.Meta `path:"/label-tasks/detail" method:"get" summary:"预标注任务详情" tags:"管理端"` Id int64 `json:"id" v:"required|min:1" dc:"标注任务 id"` } // AdminLabelBox 单框(归一化 xywh,坐标 0~1) type AdminLabelBox struct { Cx float64 `json:"cx"` Cy float64 `json:"cy"` W float64 `json:"w"` H float64 `json:"h"` Confidence float64 `json:"confidence"` // 人工框为 1 Class int `json:"class"` // 0 确认 | 1 疑似 } // AdminLabelImageItem 工作台单张图:全部标注(labels_json,AI 自动标注与人工框同层,人工可修改/清理) type AdminLabelImageItem struct { Filename string `json:"filename"` Url string `json:"url"` Width int `json:"width"` Height int `json:"height"` Boxes []*AdminLabelBox `json:"boxes"` Labeled bool `json:"labeled"` // 有标注(AI 自动标注或人工保存) } type AdminLabelTaskDetailRes struct { TaskId int64 `json:"taskId"` DatasetId int64 `json:"datasetId"` DatasetName string `json:"datasetName"` Status string `json:"status"` Total int `json:"total"` Done int `json:"done"` Error string `json:"error"` Images []*AdminLabelImageItem `json:"images"` } // AdminLabelWorkbenchReq 标注工作台数据(无历史任务时直接用数据集图片 + 已确认标注, // 候选框读数据集目录 boxes.json) type AdminLabelWorkbenchReq struct { g.Meta `path:"/label-workbench" method:"get" summary:"标注工作台数据" tags:"管理端"` DatasetId int64 `json:"datasetId" v:"required|min:1" dc:"数据集 id"` } type AdminLabelWorkbenchRes struct { DatasetId int64 `json:"datasetId"` DatasetName string `json:"datasetName"` Images []*AdminLabelImageItem `json:"images"` } // AdminLabelSaveReq 保存单张图标注(工作台「保存本张」;覆写 dataset_image.labels_json) type AdminLabelSaveReq struct { g.Meta `path:"/label-tasks/save" method:"post" summary:"保存图片标注" tags:"管理端"` DatasetId int64 `json:"datasetId" v:"required|min:1" dc:"数据集 id"` Filename string `json:"filename" v:"required|length:1,200" dc:"图片文件名"` Boxes []*AdminLabelBox `json:"boxes" dc:"确认后的框列表(空=清空标注)"` } type AdminLabelSaveRes struct { LabeledCount int64 `json:"labeledCount"` // 该数据集当前已标注图数 } // ---------- 客户端模型目录 ---------- // ModelCatalogReq 模型目录(客户端登录态):全部数据集的当前生效模型 type ModelCatalogReq struct { g.Meta `path:"/models" method:"get" summary:"模型目录" tags:"客户端"` } // ModelCatalogItem 客户端模型条目(App 按需下载,多模型并行推理合并) type ModelCatalogItem struct { DatasetId int64 `json:"datasetId"` DatasetName string `json:"datasetName"` Version string `json:"version"` Labels []string `json:"labels"` // 类别名,App 推理结果展示用 SizeBytes int64 `json:"sizeBytes"` Sha256 string `json:"sha256"` Notes string `json:"notes"` PublishedAt *gtime.Time `json:"publishedAt"` DownloadUrl string `json:"downloadUrl"` // /download/trainings/<文件名前缀>.tflite(前缀空回退数据集名) CoverUrl string `json:"coverUrl"` // /api/v1/app/cover?namePrefix=<前缀>(App 模型清单缩略图) } type ModelCatalogRes struct { List []*ModelCatalogItem `json:"list"` }