1
This commit is contained in:
@@ -45,7 +45,4 @@ const (
|
||||
|
||||
// 模型版本号前缀(m1.0.0),同数据集内递增
|
||||
ModelVersionPrefix = "m"
|
||||
|
||||
// AI 生成图距离/数量校验:单张不合格重生成次数上限(连续不合格即报错返回,防死循环烧钱)
|
||||
GenValidateMaxAttempts = 3
|
||||
)
|
||||
|
||||
@@ -122,6 +122,14 @@ func (d *datasetDao) UpdateConfigs(ctx context.Context, id int64, m *entity.Data
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateDescription 更新描述(描述是自由文本,允许清空:空值也写入;
|
||||
// UpdateConfigs 空值不覆盖,清空描述无法复用,2026-08-28)
|
||||
func (d *datasetDao) UpdateDescription(ctx context.Context, id int64, description string) error {
|
||||
_, err := g.DB().Model(consts.TableDataset).Ctx(ctx).Where("id", id).
|
||||
Data(g.Map{"description": description, "updated_at": gtime.Now()}).Update()
|
||||
return err
|
||||
}
|
||||
|
||||
// ClearCover 清空封面字段(删除封面用;UpdateConfigs 空值不覆盖,无法复用)
|
||||
func (d *datasetDao) ClearCover(ctx context.Context, id int64) error {
|
||||
_, err := g.DB().Model(consts.TableDataset).Ctx(ctx).Where("id", id).Data(g.Map{"cover": "", "updated_at": gtime.Now()}).Update()
|
||||
|
||||
@@ -96,20 +96,17 @@ type AdminDatasetUploadRes struct {
|
||||
}
|
||||
|
||||
// AdminDatasetGenerateReq AI 生成图片(同步执行;prompt 可手填覆盖模板,禁止含目标位置描述;
|
||||
// 未手填走 imageGen.promptTemplate 组装:species/scene 用表单选择,数量词=animalCount。
|
||||
// distanceMin/distanceMax(米)与 animalCount 填写后逐张校验:RF-DETR 检测目标框(去重口径),
|
||||
// 数量精确匹配、估算距离(物理公式)越界自动重新生成(上限 consts.GenValidateMaxAttempts 次)。
|
||||
// 文件名前缀为数据集属性(datasets 表 name_prefix),不再随生成请求传入
|
||||
// 未手填走 imageGen.promptTemplate 组装:species 用表单选择(scene/action/light 随机),数量词=animalCount。
|
||||
// animalCount 仅作为标注框数上限存储(自动标注按置信度裁剪 ≤N),不参与生成校验;
|
||||
// 距离校验已取消(2026-08-28)。文件名前缀为数据集属性(datasets 表 name_prefix)
|
||||
type AdminDatasetGenerateReq struct {
|
||||
g.Meta `path:"/datasets/generate" method:"post" summary:"AI 生成图片" tags:"管理端"`
|
||||
DatasetId int64 `json:"datasetId" v:"required|min:1" dc:"数据集 id"`
|
||||
Species string `json:"species" v:"length:0,50" dc:"动物名称(如家鸽/山斑鸠;留空按数据集名取物种池随机)"`
|
||||
Prompt string `json:"prompt" v:"length:0,500" dc:"完整提示词(可选,填写则覆盖模板;禁止含位置描述)"`
|
||||
Count int `json:"count" v:"required|integer|min:1|max:8" dc:"生成张数 1-8"`
|
||||
Size string `json:"size" v:"required|in:704x1248" dc:"图片尺寸"`
|
||||
DistanceMin int `json:"distanceMin" v:"integer|min:0" dc:"距离范围下限(米,0=不校验距离)"`
|
||||
DistanceMax int `json:"distanceMax" v:"integer|min:0" dc:"距离范围上限(米,0=不校验距离)"`
|
||||
AnimalCount int `json:"animalCount" v:"integer|min:0|max:20" dc:"画面目标数量(精确匹配,0=不校验;标注框数不超过该值)"`
|
||||
g.Meta `path:"/datasets/generate" method:"post" summary:"AI 生成图片" tags:"管理端"`
|
||||
DatasetId int64 `json:"datasetId" v:"required|min:1" dc:"数据集 id"`
|
||||
Species string `json:"species" v:"length:0,50" dc:"动物名称(如家鸽/山斑鸠;留空按数据集名取物种池随机)"`
|
||||
Prompt string `json:"prompt" v:"length:0,500" dc:"完整提示词(可选,填写则覆盖模板;禁止含位置描述)"`
|
||||
Count int `json:"count" v:"required|integer|min:1|max:8" dc:"生成张数 1-8"`
|
||||
Size string `json:"size" v:"required|in:704x1248" dc:"图片尺寸"`
|
||||
Distance int `json:"distance" v:"required|min:1" dc:"目标距离(米,固定值,注入提示词)"`
|
||||
}
|
||||
|
||||
// AdminDatasetGenOptionsReq 生成表单选项(物种池/场景池,来自 config imageGen 配置)
|
||||
@@ -124,7 +121,8 @@ type AdminDatasetGenOptionsRes struct {
|
||||
}
|
||||
|
||||
type AdminDatasetGenerateRes struct {
|
||||
Generated int `json:"generated"` // 成功张数(失败时可能 < count,已成功的图保留)
|
||||
Generated int `json:"generated"` // 成功入库张数(校验不合格的已跳过)
|
||||
Rejected int `json:"rejected"` // 连续 N 次校验不合格被跳过的张数
|
||||
}
|
||||
|
||||
// AdminDatasetImagesReq 数据集图片列表(标注工作台/网格预览用)
|
||||
|
||||
+101
-137
@@ -133,13 +133,16 @@ func (s *datasetService) AdminUpdateDataset(ctx context.Context, req *dto.AdminD
|
||||
return nil, gerror.New("文件名前缀仅支持字母/数字/下划线/短横线,长度 1-50")
|
||||
}
|
||||
if err := dao.Dataset.UpdateConfigs(ctx, req.Id, &entity.Dataset{
|
||||
Name: newName,
|
||||
NamePrefix: prefix,
|
||||
Cover: req.Cover,
|
||||
Description: req.Description,
|
||||
Name: newName,
|
||||
NamePrefix: prefix,
|
||||
Cover: req.Cover,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// 描述允许清空(UpdateConfigs 空值不覆盖),单独更新
|
||||
if err := dao.Dataset.UpdateDescription(ctx, req.Id, strings.TrimSpace(req.Description)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &dto.AdminDatasetUpdateRes{Id: req.Id}, nil
|
||||
}
|
||||
|
||||
@@ -570,67 +573,87 @@ func (s *datasetService) nextImageIndex(ctx context.Context, datasetId int64, pr
|
||||
return max + 1, nil
|
||||
}
|
||||
|
||||
// validateGeneratedImage 生成图距离/数量校验:RF-DETR 全图检测目标框(与自动标注同口径去重),
|
||||
// 数量精确匹配 animalCount;距离用物理公式 d = 物种体高 × focalPx ÷ 目标像素高
|
||||
// (focalPx 由假设视场角 imageGen.assumedVfovDeg 推出;体高按物种查 imageGen.speciesHeights),
|
||||
// 须落在表单 [distanceMin, distanceMax] 内(>0 才启用对应项)
|
||||
func (s *datasetService) validateGeneratedImage(ctx context.Context, data []byte, req *dto.AdminDatasetGenerateReq, species string) (bool, string, error) {
|
||||
client := common.LocalAiClient(ctx)
|
||||
if client == nil {
|
||||
return false, "", gerror.NewCode(common.CodeLocalAiNotConfigured)
|
||||
}
|
||||
// 校验与预标注用途不同:预标注阈值 0.08 宁多勿漏,会把玉米叶/土块算成目标(实测 30-40m 场景 6-18 框噪声);
|
||||
// 校验用高置信度阈值(实测 0.3 时真鸟全留、噪声清零,0.4 起连远处真鸟都丢)
|
||||
client.Threshold = g.Cfg().MustGet(ctx, "imageGen.validateThreshold", 0.3).Float64()
|
||||
imgW, imgH, err := parseGenSize(req.Size)
|
||||
// AdminGenOptions 生成表单选项:该数据集的物种池 + 场景池(config 维护,前端下拉用)
|
||||
func (s *datasetService) AdminGenOptions(ctx context.Context, req *dto.AdminDatasetGenOptionsReq) (*dto.AdminDatasetGenOptionsRes, error) {
|
||||
dataset, err := dao.Dataset.GetById(ctx, req.DatasetId)
|
||||
if err != nil {
|
||||
return false, "", err
|
||||
return nil, err
|
||||
}
|
||||
dets, err := client.Detect(ctx, data, "image/jpeg", imgW, imgH)
|
||||
if err != nil {
|
||||
return false, "", gerror.Wrap(err, "生成图检测失败")
|
||||
if dataset == nil {
|
||||
return nil, gerror.NewCode(common.CodeDatasetNotFound)
|
||||
}
|
||||
// 与自动标注同口径去重(同目标多框只计一只),保证「校验数量 == 标注框数」
|
||||
cands := make([]*dto.AdminLabelBox, 0, len(dets))
|
||||
for _, d := range dets {
|
||||
cands = append(cands, &dto.AdminLabelBox{
|
||||
Cx: (d.X + d.Width/2) / float64(imgW),
|
||||
Cy: (d.Y + d.Height/2) / float64(imgH),
|
||||
W: d.Width / float64(imgW),
|
||||
H: d.Height / float64(imgH),
|
||||
Confidence: d.Confidence,
|
||||
})
|
||||
return &dto.AdminDatasetGenOptionsRes{
|
||||
Species: g.Cfg().MustGet(ctx, "imageGen.speciesByDataset."+dataset.Name).Strings(),
|
||||
Scenes: g.Cfg().MustGet(ctx, "imageGen.scenes").Strings(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// singleAnimalClause 目标数量约束:每张仅一只动物,手填提示词统一追加(模板已由 {count} 固定 "1只")
|
||||
const singleAnimalClause = "画面中有且只有这一只动物,没有任何其他动物"
|
||||
|
||||
// buildPromptFromTemplate 按 config 通用模板组装提示词:物种/场景优先用表单选择
|
||||
// (物种只写名字不写羽毛细节——模型对正确名称自带外观先验;场景未选则池内随机),
|
||||
// 物种未填按数据集名取物种池随机(池也未配置则用数据集名本身);动作/光线池内随机。
|
||||
func (s *datasetService) buildPromptFromTemplate(ctx context.Context, datasetName, species string, dist int, size string) (string, error) {
|
||||
tpl := g.Cfg().MustGet(ctx, "imageGen.promptTemplate").String()
|
||||
if tpl == "" {
|
||||
return "", gerror.New("未配置通用提示词模板(imageGen.promptTemplate),请手填提示词")
|
||||
}
|
||||
deduped := suppressOverlap(cands, client.OverlapThreshold)
|
||||
if len(deduped) == 0 {
|
||||
return false, "未检测到目标动物", nil
|
||||
}
|
||||
// 数量为精确校验:表单指定几只,画面里就必须恰好几只(提示词中的数量应与表单一致)
|
||||
if req.AnimalCount > 0 && len(deduped) != req.AnimalCount {
|
||||
return false, fmt.Sprintf("目标数量 %d 与指定数量 %d 不符", len(deduped), req.AnimalCount), nil
|
||||
}
|
||||
maxRatio := 0.0
|
||||
for _, b := range deduped {
|
||||
if b.H > maxRatio {
|
||||
maxRatio = b.H
|
||||
if species == "" {
|
||||
species = datasetName
|
||||
if pool := g.Cfg().MustGet(ctx, "imageGen.speciesByDataset."+datasetName).Strings(); len(pool) > 0 {
|
||||
species = pool[rand.Intn(len(pool))]
|
||||
}
|
||||
}
|
||||
// 物理公式:d = 体高 × focalPx ÷ 像素高;focalPx = 图高/2 ÷ tan(vfov/2)
|
||||
heightM := g.Cfg().MustGet(ctx, "imageGen.speciesHeights.default", 0.35).Float64()
|
||||
if h := g.Cfg().MustGet(ctx, "imageGen.speciesHeights."+species).Float64(); h > 0 {
|
||||
heightM = h
|
||||
// 场景/动作按数据集习性取池(鱼水域、雉鸡灌丛……),未配置该数据集回退通用池
|
||||
scene := pickCfgByDataset(ctx, datasetName, "imageGen.sceneByDataset", "imageGen.scenes")
|
||||
action := pickCfgByDataset(ctx, datasetName, "imageGen.actionByDataset", "imageGen.actions")
|
||||
light := pickCfg(ctx, "imageGen.lights")
|
||||
// 距离描述从表单固定值生成(25 → "25米外")
|
||||
distWord := fmt.Sprintf("%d米外", dist)
|
||||
// 尺寸提示:按物理公式把表单距离换算成目标在图中的像素高与画面占比
|
||||
sizeHint, sErr := s.buildSizeHint(ctx, species, dist, size)
|
||||
if sErr != nil {
|
||||
return "", sErr
|
||||
}
|
||||
// 每张固定 1 个目标(数量词固定,animal_count 列同为 1 供自动标注裁剪)
|
||||
countWord := "1只"
|
||||
out := tpl
|
||||
for k, v := range map[string]string{
|
||||
"{scene}": scene, "{species}": species, "{count}": countWord,
|
||||
"{action}": action, "{light}": light, "{distanceWord}": distWord, "{sizeHint}": sizeHint,
|
||||
} {
|
||||
out = strings.ReplaceAll(out, k, v)
|
||||
}
|
||||
g.Log().Infof(ctx, "模板组装提示词: %s", out)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// buildSizeHint 尺寸提示:目标像素高 = 体高 × focalPx ÷ 距离(focalPx = 图高/2 ÷ tan(vfov/2)),
|
||||
// 输出如「(在画面中高度仅约15像素,占画面高度约1.2%)」。
|
||||
// 占比向下取整且至少 1%,避免出现 0%
|
||||
func (s *datasetService) buildSizeHint(ctx context.Context, species string, dist int, size string) (string, error) {
|
||||
_, imgH, err := parseGenSize(size)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
// 配置单位为 cm(实际体型),换算成米参与投影公式;未配置物种兜底 35cm
|
||||
heightCM := g.Cfg().MustGet(ctx, "imageGen.speciesHeights."+species, 35.0).Float64()
|
||||
heightM := heightCM / 100
|
||||
// 镜头固定为手机默认主摄(垂直视场角配置值,默认 52°;2026-08-28 曾随机 [15,90]、曾试超广角,用户最终定为默认镜头)
|
||||
vfov := g.Cfg().MustGet(ctx, "imageGen.assumedVfovDeg", 52.0).Float64() * math.Pi / 180
|
||||
focalPx := float64(imgH) / 2 / math.Tan(vfov/2)
|
||||
pxHeight := maxRatio * float64(imgH)
|
||||
estDist := heightM * focalPx / pxHeight
|
||||
if req.DistanceMin > 0 && estDist < float64(req.DistanceMin) {
|
||||
return false, fmt.Sprintf("估算距离约 %.0f 米低于下限 %d 米(目标过近)", estDist, req.DistanceMin), nil
|
||||
pxAt := func(dist int) int {
|
||||
if dist <= 0 {
|
||||
dist = 1
|
||||
}
|
||||
return int(math.Round(heightM * focalPx / float64(dist)))
|
||||
}
|
||||
if req.DistanceMax > 0 && estDist > float64(req.DistanceMax) {
|
||||
return false, fmt.Sprintf("估算距离约 %.0f 米超出上限 %d 米(目标过远)", estDist, req.DistanceMax), nil
|
||||
pct := func(px int) string {
|
||||
return fmt.Sprintf("%.1f", float64(px)*100/float64(imgH))
|
||||
}
|
||||
return true, fmt.Sprintf("数量 %d,最大目标估算距离约 %.0f 米", len(deduped), estDist), nil
|
||||
px := pxAt(dist)
|
||||
return fmt.Sprintf("(在画面中高度仅约%d像素,占画面高度约%s%%)", px, pct(px)), nil
|
||||
}
|
||||
|
||||
// parseGenSize 解析 "704x1248" 形式尺寸
|
||||
@@ -647,57 +670,6 @@ func parseGenSize(size string) (int, int, error) {
|
||||
return w, h, nil
|
||||
}
|
||||
|
||||
// countWordCN 数量词(与表单 animalCount 精确对应,防提示词文案与校验数量不符)
|
||||
var countWordCN = [...]string{"一只", "两只", "三只", "四只", "五只", "六只", "七只", "八只", "九只", "十只"}
|
||||
|
||||
// AdminGenOptions 生成表单选项:该数据集的物种池 + 场景池(config 维护,前端下拉用)
|
||||
func (s *datasetService) AdminGenOptions(ctx context.Context, req *dto.AdminDatasetGenOptionsReq) (*dto.AdminDatasetGenOptionsRes, error) {
|
||||
dataset, err := dao.Dataset.GetById(ctx, req.DatasetId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if dataset == nil {
|
||||
return nil, gerror.NewCode(common.CodeDatasetNotFound)
|
||||
}
|
||||
return &dto.AdminDatasetGenOptionsRes{
|
||||
Species: g.Cfg().MustGet(ctx, "imageGen.speciesByDataset."+dataset.Name).Strings(),
|
||||
Scenes: g.Cfg().MustGet(ctx, "imageGen.scenes").Strings(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// buildPromptFromTemplate 按 config 通用模板组装提示词:物种/场景优先用表单选择
|
||||
// (物种只写名字不写羽毛细节——模型对正确名称自带外观先验;场景未选则池内随机),
|
||||
// 物种未填按数据集名取物种池随机(池也未配置则用数据集名本身);动作/光线池内随机。
|
||||
func (s *datasetService) buildPromptFromTemplate(ctx context.Context, datasetName, species string, count int) (string, string, error) {
|
||||
tpl := g.Cfg().MustGet(ctx, "imageGen.promptTemplate").String()
|
||||
if tpl == "" {
|
||||
return "", "", gerror.New("未配置通用提示词模板(imageGen.promptTemplate),请手填提示词")
|
||||
}
|
||||
if species == "" {
|
||||
species = datasetName
|
||||
if pool := g.Cfg().MustGet(ctx, "imageGen.speciesByDataset."+datasetName).Strings(); len(pool) > 0 {
|
||||
species = pool[rand.Intn(len(pool))]
|
||||
}
|
||||
}
|
||||
scene := pickCfg(ctx, "imageGen.scenes")
|
||||
action := pickCfg(ctx, "imageGen.actions")
|
||||
light := pickCfg(ctx, "imageGen.lights")
|
||||
dist := g.Cfg().MustGet(ctx, "imageGen.distanceWord", "远处").String()
|
||||
countWord := fmt.Sprintf("%d只", count)
|
||||
if count >= 1 && count <= len(countWordCN) {
|
||||
countWord = countWordCN[count-1]
|
||||
}
|
||||
out := tpl
|
||||
for k, v := range map[string]string{
|
||||
"{scene}": scene, "{species}": species, "{count}": countWord,
|
||||
"{action}": action, "{light}": light, "{distanceWord}": dist,
|
||||
} {
|
||||
out = strings.ReplaceAll(out, k, v)
|
||||
}
|
||||
g.Log().Infof(ctx, "模板组装提示词: %s", out)
|
||||
return out, species, nil
|
||||
}
|
||||
|
||||
// pickCfg 取字符串数组配置项随机一项(空池返回空串)
|
||||
func pickCfg(ctx context.Context, key string) string {
|
||||
pool := g.Cfg().MustGet(ctx, key).Strings()
|
||||
@@ -707,6 +679,15 @@ func pickCfg(ctx context.Context, key string) string {
|
||||
return pool[rand.Intn(len(pool))]
|
||||
}
|
||||
|
||||
// pickCfgByDataset 按数据集名取配置池(key 形如 imageGen.sceneByDataset.<数据集名>)随机,
|
||||
// 该数据集未配置时回退通用池
|
||||
func pickCfgByDataset(ctx context.Context, datasetName, byDatasetKey, genericKey string) string {
|
||||
if pool := g.Cfg().MustGet(ctx, byDatasetKey+"."+datasetName).Strings(); len(pool) > 0 {
|
||||
return pool[rand.Intn(len(pool))]
|
||||
}
|
||||
return pickCfg(ctx, genericKey)
|
||||
}
|
||||
|
||||
// AdminGenerateImages AI 生成图片:同步逐张生成(每张超时 imageGen.timeoutSeconds),逐张落盘 + 入库;
|
||||
// 中途失败返回错误并附已成功张数(已生成的图为付费资产,保留不删除)。
|
||||
func (s *datasetService) AdminGenerateImages(ctx context.Context, req *dto.AdminDatasetGenerateReq) (*dto.AdminDatasetGenerateRes, error) {
|
||||
@@ -754,6 +735,7 @@ func (s *datasetService) AdminGenerateImages(ctx context.Context, req *dto.Admin
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
rejected := 0
|
||||
for i := 0; i < req.Count; i++ {
|
||||
// 部分成功也要标注:报错返回前对已入库图片触发自动标注(外部重试驱动依赖此语义攒量)
|
||||
failWith := func(err error) (*dto.AdminDatasetGenerateRes, error) {
|
||||
@@ -764,15 +746,21 @@ func (s *datasetService) AdminGenerateImages(ctx context.Context, req *dto.Admin
|
||||
}
|
||||
}
|
||||
}
|
||||
return &dto.AdminDatasetGenerateRes{Generated: generated}, err
|
||||
return &dto.AdminDatasetGenerateRes{Generated: generated, Rejected: rejected}, err
|
||||
}
|
||||
// 提示词:手填优先;未手填走 config 通用模板逐张组装——物种/场景用表单选择,
|
||||
// 未填按数据集名取物种池随机、场景池随机;数量词与表单 animalCount 一致;动作/光线随机
|
||||
// 提示词:手填优先;未手填走 config 通用模板逐张组装——物种用表单选择(未填池内随机),
|
||||
// 场景/动作/光线随机。距离校验已取消(2026-08-28)
|
||||
prompt := strings.TrimSpace(req.Prompt)
|
||||
species := strings.TrimSpace(req.Species)
|
||||
if handWritten {
|
||||
// 手填提示词统一追加数量约束:仅一只动物(拍摄角度不带,交由模型平视先验自由构图)
|
||||
prompt = strings.TrimSuffix(prompt, "。") + "。"
|
||||
if !strings.Contains(prompt, "一只") && !strings.Contains(prompt, "1只") && !strings.Contains(prompt, "唯一") {
|
||||
prompt += singleAnimalClause
|
||||
}
|
||||
}
|
||||
if !handWritten {
|
||||
var tErr error
|
||||
if prompt, species, tErr = s.buildPromptFromTemplate(ctx, dataset.Name, species, req.AnimalCount); tErr != nil {
|
||||
if prompt, tErr = s.buildPromptFromTemplate(ctx, dataset.Name, strings.TrimSpace(req.Species), req.Distance, req.Size); tErr != nil {
|
||||
return nil, tErr
|
||||
}
|
||||
}
|
||||
@@ -781,30 +769,6 @@ func (s *datasetService) AdminGenerateImages(ctx context.Context, req *dto.Admin
|
||||
// 付费资产保留原则:已生成的不删除
|
||||
return failWith(gerror.NewCode(common.CodeImageGenFailed, fmt.Sprintf("第 %d 张生成失败(已生成 %d 张): %v", i+1, generated, genErr)))
|
||||
}
|
||||
// 逐张校验(表单填写距离范围/目标数量后启用):不合格丢弃并重新生成,连续不合格报错返回
|
||||
needValidate := req.DistanceMax > 0 || req.DistanceMin > 0 || req.AnimalCount > 0
|
||||
if needValidate {
|
||||
passed := false
|
||||
reason := ""
|
||||
for attempt := 1; attempt <= consts.GenValidateMaxAttempts; attempt++ {
|
||||
ok, r, vErr := s.validateGeneratedImage(ctx, data, req, species)
|
||||
if vErr != nil {
|
||||
return failWith(gerror.Wrapf(vErr, "第 %d 张校验失败(已生成 %d 张)", i+1, generated))
|
||||
}
|
||||
if ok {
|
||||
passed = true
|
||||
break
|
||||
}
|
||||
reason = r
|
||||
g.Log().Infof(ctx, "生成图校验不合格(%s),第 %d 张第 %d 次重新生成", r, i+1, attempt)
|
||||
if data, genErr = genOnce(prompt); genErr != nil {
|
||||
return failWith(gerror.NewCode(common.CodeImageGenFailed, fmt.Sprintf("第 %d 张重新生成失败(已生成 %d 张): %v", i+1, generated, genErr)))
|
||||
}
|
||||
}
|
||||
if !passed {
|
||||
return failWith(gerror.NewCode(common.CodeImageGenFailed, fmt.Sprintf("第 %d 张连续 %d 次校验不合格(%s),请调整提示词或范围后重试(已生成 %d 张)", i+1, consts.GenValidateMaxAttempts, reason, generated)))
|
||||
}
|
||||
}
|
||||
filename := ""
|
||||
if prefix != "" {
|
||||
filename = fmt.Sprintf("%s_%02d.jpg", prefix, nextIdx)
|
||||
@@ -822,7 +786,7 @@ func (s *datasetService) AdminGenerateImages(ctx context.Context, req *dto.Admin
|
||||
Filename: filename,
|
||||
Source: "ai",
|
||||
Prompt: prompt,
|
||||
AnimalCount: req.AnimalCount,
|
||||
AnimalCount: 1,
|
||||
CreatedAt: now,
|
||||
})
|
||||
imgId = id
|
||||
@@ -846,10 +810,10 @@ func (s *datasetService) AdminGenerateImages(ctx context.Context, req *dto.Admin
|
||||
return nil, err
|
||||
}
|
||||
if err := LabelTask.AutoLabel(ctx, dataset, newImages); err != nil {
|
||||
return &dto.AdminDatasetGenerateRes{Generated: generated},
|
||||
return &dto.AdminDatasetGenerateRes{Generated: generated, Rejected: rejected},
|
||||
gerror.Wrap(err, "图片已生成入库,但自动标注触发失败")
|
||||
}
|
||||
return &dto.AdminDatasetGenerateRes{Generated: generated}, nil
|
||||
return &dto.AdminDatasetGenerateRes{Generated: generated, Rejected: rejected}, nil
|
||||
}
|
||||
|
||||
// AdminListImages 数据集图片列表(创建时间正序,标注工作台/网格预览)
|
||||
|
||||
Reference in New Issue
Block a user