This commit is contained in:
2026-08-28 14:31:26 +08:00
parent 1cf7320587
commit 283455eb52
2 changed files with 103 additions and 26 deletions
+82 -15
View File
@@ -62,15 +62,27 @@ const addVisible = ref(false)
const addMode = ref('upload') // upload | ai
const uploadList = ref([])
const uploading = ref(false)
const genForm = reactive({ prompt: '', count: 1, size: '704x1248' })
const genForm = reactive({ species: '', count: 1, size: '704x1248', animalCount: 3, distanceMin: 20, distanceMax: 30 })
const speciesOptions = ref([])
const generating = ref(false)
// 生成表单选项(物种池/场景池来自 config,随配置热更新)
function loadGenOptions() {
request.get('/datasets/gen-options', { params: { datasetId: datasetId.value } }).then((d) => {
speciesOptions.value = d.species || []
}).catch(() => {})
}
function openAdd() {
addMode.value = 'upload'
uploadList.value = []
genForm.prompt = ''
genForm.species = ''
loadGenOptions()
genForm.count = 1
genForm.size = '704x1248'
genForm.animalCount = 3
genForm.distanceMin = 20
genForm.distanceMax = 30
addVisible.value = true
}
@@ -98,17 +110,20 @@ function submitUpload() {
}
function submitGen() {
if (!genForm.prompt.trim() || genForm.prompt.trim().length < 5) {
ElMessage.warning('请输入提示词(至少 5 个字符)')
if (genForm.distanceMax < genForm.distanceMin) {
ElMessage.warning('距离范围上限不能小于下限')
return
}
generating.value = true
request
.post('/datasets/generate', {
datasetId: datasetId.value,
prompt: genForm.prompt.trim(),
species: genForm.species.trim(),
count: genForm.count,
size: genForm.size,
animalCount: genForm.animalCount,
distanceMin: genForm.distanceMin,
distanceMax: genForm.distanceMax,
}, { timeout: 2400000 })
.then((data) => {
ElMessage.success(`已生成 ${data.generated || 0} 张(生成图片为付费资产,删除前请确认),自动标注处理中…`)
@@ -170,6 +185,28 @@ function startFullLabelTask() {
.catch(() => {})
}
// VLM 藏匿位补检(两阶段标注第二阶段):排除已确认框,按环境/光线/习性找可能藏匿处,追加疑似框
const vlmReviewing = ref(false)
async function vlmReview() {
if (!currentImage.value) return
const m = meta(currentImage.value)
if (!m.id) return
vlmReviewing.value = true
try {
const d = await request.post('/datasets/images/vlm-review', { datasetId: datasetId.value, imageId: m.id }, { timeout: 300000 })
if (d.added > 0) {
ElMessage.success(`VLM 补检新增 ${d.added} 个疑似框(黄框,请人工确认)`)
} else {
ElMessage.info(d.note || 'VLM 未发现可能的藏匿位')
}
dirty.value = false
await loadAll()
nextTick(resetEditor)
} finally {
vlmReviewing.value = false
}
}
// 删除单张图片(标注随图片删除,后端级联)
function removeCardImage(item) {
const m = meta(item)
@@ -619,21 +656,38 @@ onBeforeUnmount(() => {
</div>
<el-form v-else label-width="90px">
<el-form-item label="提示词">
<el-input
v-model="genForm.prompt"
type="textarea"
:rows="4"
maxlength="500"
show-word-limit
placeholder="描述拍摄场景与目标外观,例如:户外田野背景,一只色彩鲜艳的野生雉鸡站立在草丛中,全身清晰可见。注意:不要描述目标在画面中的位置"
/>
<el-form-item label="动物名称">
<el-select
v-model="genForm.species"
filterable
allow-create
default-first-option
clearable
placeholder="留空按数据集物种池随机"
style="width: 100%"
>
<el-option v-for="s in speciesOptions" :key="s" :label="s" :value="s" />
</el-select>
</el-form-item>
<el-form-item label="生成张数">
<el-input-number v-model="genForm.count" :min="1" :max="8" />
</el-form-item>
<el-form-item label="目标数量">
<div class="gen-inline">
<el-input-number v-model="genForm.animalCount" :min="1" :max="20" />
<span class="gen-note">画面中目标必须正好是该数量提示词中的数量写法应与此一致不符自动重新生成标注框数不超过该数量</span>
</div>
</el-form-item>
<el-form-item label="距离范围">
<div class="gen-inline">
<el-input-number v-model="genForm.distanceMin" :min="1" :max="500" />
<span class="gen-note"></span>
<el-input-number v-model="genForm.distanceMax" :min="1" :max="500" />
<span class="gen-note">逐张校验距离不合格自动重新生成</span>
</div>
</el-form-item>
<el-form-item label="图片尺寸">
<span class="gen-size">竖版 704x1248很慢 26 分钟/建议一次只生成 1 </span>
<span class="gen-size">竖版 704x1248</span>
</el-form-item>
<div class="gen-tip">生成图片为付费资产请勿随意删除提示词中不要描述目标在画面中的位置由模型自行构图</div>
</el-form>
@@ -672,6 +726,7 @@ onBeforeUnmount(() => {
<el-radio-button :value="1"><i class="dot yellow" />疑似</el-radio-button>
</el-radio-group>
<el-button size="small" :disabled="!confirmed.length" @click="clearAllBoxes">清空本张</el-button>
<el-button size="small" type="warning" :loading="vlmReviewing" @click="vlmReview">VLM 补检</el-button>
<el-divider direction="vertical" />
<span class="zoom-ctrl">
<el-button size="small" circle :icon="Plus" @click="zoomIn" />
@@ -825,6 +880,18 @@ onBeforeUnmount(() => {
color: #909399;
line-height: 1.6;
}
/* 生成表单行内组合(数量/距离范围 + 说明) */
.gen-inline {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
}
.gen-note {
font-size: 12px;
color: #909399;
line-height: 1.6;
}
/* 上传方块:虚线边框 + 居中加号(el-upload picture-card 触发块) */
.upload-tile {
width: 100%;
+21 -11
View File
@@ -15,11 +15,12 @@ const keyword = ref('')
const formVisible = ref(false)
const saving = ref(false)
// 新建/编辑共用弹窗:edit 模式下名称只读(服务端 update 接口不支持改名
// 新建/编辑共用弹窗(改名/前缀/描述/封面;改名同步迁移磁盘目录
const dlgForm = reactive({
mode: 'create', // create | edit
id: 0,
name: '',
namePrefix: '',
description: '',
cover: '',
})
@@ -60,7 +61,7 @@ function resetCoverState() {
}
function openCreate() {
Object.assign(dlgForm, { mode: 'create', id: 0, name: '', description: '', cover: '' })
Object.assign(dlgForm, { mode: 'create', id: 0, name: '', namePrefix: '', description: '', cover: '' })
resetCoverState()
coverFileList.value = []
formVisible.value = true
@@ -71,6 +72,7 @@ function openEdit(row) {
mode: 'edit',
id: row.id,
name: row.name,
namePrefix: row.namePrefix || '',
description: row.description || '',
cover: row.cover || '',
})
@@ -111,7 +113,7 @@ async function submitDlg() {
try {
let id = dlgForm.id
if (dlgForm.mode === 'create') {
id = (await request.post('/datasets', { name, source: 'manual' })).id
id = (await request.post('/datasets', { name, namePrefix: dlgForm.namePrefix.trim(), source: 'manual' })).id
}
if (coverFile.value) {
const fd = new FormData()
@@ -123,7 +125,7 @@ async function submitDlg() {
}
// create 且未填描述时跳过 update(服务端空值不覆盖,避免空跑一次)
if (dlgForm.mode === 'edit' || dlgForm.description.trim()) {
await request.post('/datasets/update', { id, name, description: dlgForm.description.trim(), cover: '' })
await request.post('/datasets/update', { id, name, namePrefix: dlgForm.namePrefix.trim(), description: dlgForm.description.trim(), cover: '' })
}
ElMessage.success(dlgForm.mode === 'create' ? '数据集已创建,请上传或生成图片' : '已保存')
formVisible.value = false
@@ -266,10 +268,19 @@ onBeforeUnmount(() => {
</el-button>
<el-button size="small" :icon="EditPen" @click="openEdit(row)">编辑</el-button>
<el-button size="small" type="danger" @click="removeDataset(row)">删除</el-button>
<!-- 训练成功且未发布过发布按钮与其他操作同行失败标签在下方状态行展示 -->
<el-button
v-if="row.trainingStatus === 'success' && row.trainingId && !row.trainingPublished"
size="small"
type="success"
@click="publishModel(row)"
>
发布模型
</el-button>
</div>
<!-- 训练任务进度与状态最新一条训练记录 -->
<div v-if="row.trainingStatus" class="ds-train" @click.stop>
<!-- 训练任务进度与状态最新一条训练记录成功态只留操作行按钮不占独立行 -->
<div v-if="row.trainingStatus && row.trainingStatus !== 'success'" class="ds-train" @click.stop>
<template v-if="row.trainingStatus === 'running'">
<el-progress :percentage="trainPercent(row)" :stroke-width="6" :show-text="false" class="ds-train-bar" />
<div class="ds-train-text">
@@ -282,13 +293,9 @@ onBeforeUnmount(() => {
{{ trainStatusMap[row.trainingStatus] || row.trainingStatus }}
</el-tag>
</el-tooltip>
<!-- 训练成功不显示已训练标签只保留发布按钮 -->
<el-tag v-else-if="row.trainingStatus !== 'success'" :type="trainStatusTag[row.trainingStatus] || 'info'" size="small">
<el-tag v-else :type="trainStatusTag[row.trainingStatus] || 'info'" size="small">
{{ trainStatusMap[row.trainingStatus] || row.trainingStatus }}
</el-tag>
<el-button v-if="row.trainingStatus === 'success' && row.trainingId" size="small" type="success" @click="publishModel(row)">
发布模型
</el-button>
</template>
</div>
</div>
@@ -316,6 +323,9 @@ onBeforeUnmount(() => {
placeholder="中文/字母/数字/下划线/短横线,唯一且作磁盘目录名"
/>
</el-form-item>
<el-form-item label="文件名前缀">
<el-input v-model="dlgForm.namePrefix" maxlength="50" placeholder="可选,如 pigeonAI 生成图按 pigeon_01.jpg 顺序命名;留空用时间戳命名" />
</el-form-item>
<el-form-item label="描述">
<el-input v-model="dlgForm.description" type="textarea" :rows="6" maxlength="500" show-word-limit placeholder="数据集说明,展示在卡片上" />
</el-form-item>