From 283455eb523f5844841378309f2f502e491ee157 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=96=8C?= <259278618@qq.com> Date: Fri, 28 Aug 2026 14:31:26 +0800 Subject: [PATCH] 1 --- server_admin/src/views/DatasetDetail.vue | 97 ++++++++++++++++++++---- server_admin/src/views/Datasets.vue | 32 +++++--- 2 files changed, 103 insertions(+), 26 deletions(-) diff --git a/server_admin/src/views/DatasetDetail.vue b/server_admin/src/views/DatasetDetail.vue index 3886cb9..e860c16 100644 --- a/server_admin/src/views/DatasetDetail.vue +++ b/server_admin/src/views/DatasetDetail.vue @@ -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(() => { - - + + + + + +
+ + 画面中目标必须正好是该数量(提示词中的数量写法应与此一致),不符自动重新生成;标注框数不超过该数量 +
+
+ +
+ + + + 米,逐张校验距离,不合格自动重新生成 +
+
- 竖版 704x1248(很慢,约 26 分钟/张,建议一次只生成 1 张) + 竖版 704x1248
生成图片为付费资产,请勿随意删除;提示词中不要描述目标在画面中的位置(由模型自行构图)。
@@ -672,6 +726,7 @@ onBeforeUnmount(() => { 疑似 清空本张 + VLM 补检 @@ -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%; diff --git a/server_admin/src/views/Datasets.vue b/server_admin/src/views/Datasets.vue index 1152af4..1afbb7e 100644 --- a/server_admin/src/views/Datasets.vue +++ b/server_admin/src/views/Datasets.vue @@ -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(() => { 编辑 删除 + + + 发布模型 + - -
+ +
@@ -316,6 +323,9 @@ onBeforeUnmount(() => { placeholder="中文/字母/数字/下划线/短横线,唯一且作磁盘目录名" /> + + +