auto-save 2026-05-18 07:05 (~8)

This commit is contained in:
2026-05-18 07:06:00 +08:00
parent d72bf62a97
commit 5fde9f3e22
8 changed files with 164 additions and 35 deletions

View File

@@ -368,10 +368,10 @@ function audioModelTrace(models?: RuntimeModels): ModelTraceSpec {
function productModelTrace(models?: RuntimeModels): ModelTraceSpec {
return {
title: "产品视角识别 / 补图",
model: modelList([models?.vision, models?.image]),
model: modelList([models?.product_view, models?.image]),
chain: [
`批量视角识别:${modelValue(models?.vision)} 一次读取同一产品多张图,标注视角、左右、上下、用途和风险`,
`缺角度补图:${imageModelChain(models)} 按同一肩颈按摩仪结构补齐缺失视角`,
`批量视角识别:${modelValue(models?.product_view)} 一次读取同一产品多张图,标注视角、左右、上下、用途和风险`,
`缺角度补图:${imageModelChain(models)} 读取最相关的多张已上传参考图,按同一肩颈按摩仪结构补齐缺失视角`,
"前端只保存标注和 AI 补图结果;后续生成视频时每条最多挑 6 张相关产品图",
],
note: "上传产品图、重新识别、缺视角重试都会使用这组模型链路。",
@@ -620,6 +620,55 @@ function createProductRefItem(
}
}
const PRODUCT_ANGLE_REFERENCE_PRIORITY: Record<string, string[]> = {
front: ["front", "left_45", "right_45", "side_thickness", "inner_contacts", "back_bottom"],
left_45: ["left_45", "front", "side_thickness", "right_45", "inner_contacts", "back_bottom"],
right_45: ["right_45", "front", "side_thickness", "left_45", "inner_contacts", "back_bottom"],
side_thickness: ["side_thickness", "left_45", "right_45", "front", "inner_contacts", "back_bottom"],
inner_contacts: ["inner_contacts", "side_thickness", "front", "left_45", "right_45", "back_bottom"],
back_bottom: ["back_bottom", "side_thickness", "inner_contacts", "left_45", "right_45", "front"],
}
function productAngleReferenceScore(item: ProductRefItem, targetView: string) {
const priority = PRODUCT_ANGLE_REFERENCE_PRIORITY[targetView] ?? PRODUCT_VIEW_SLOTS.map((slot) => slot.value)
const rank = priority.indexOf(item.view)
let score = rank === -1 ? 0 : 90 - rank * 12
if (item.source === "upload" || item.source === "library") score += 28
if (item.source === "ai") score -= 18
if (item.confidence) score += Math.round(item.confidence * 14)
if (item.useTags.includes("asymmetry")) score += 8
if (targetView === "side_thickness" && item.useTags.includes("side_thickness")) score += 16
if (targetView === "inner_contacts" && item.useTags.includes("inner_contact")) score += 16
if (targetView === "back_bottom" && item.useTags.includes("back_bottom")) score += 16
if (item.risk) score -= 10
return score
}
function selectProductAngleReferenceItems(items: ProductRefItem[], targetView: string) {
const unique = new Map<string, ProductRefItem>()
for (const item of items) {
if (!unique.has(item.id)) unique.set(item.id, item)
}
return [...unique.values()]
.sort((a, b) => productAngleReferenceScore(b, targetView) - productAngleReferenceScore(a, targetView))
.slice(0, 6)
}
function productAngleSourceNotes(items: ProductRefItem[]) {
return items.map((item, index) => {
const parts = [
`ref${index + 1}`,
`view=${productViewLabel(item.view)}`,
`source=${item.source}`,
item.note ? `note=${item.note}` : "",
formatProductOrientation(item.orientation),
item.landmarks?.length ? `landmarks=${item.landmarks.join("/")}` : "",
item.risk ? `risk=${item.risk}` : "",
].filter(Boolean)
return parts.join("")
})
}
function normalizeStoredProductItem(item: ProductRefItem, index: number): ProductRefItem {
const ref = { ...item.ref, asset_meta: item.ref.asset_meta ?? item.assetMeta }
const restored = createProductRefItem(
@@ -1960,10 +2009,13 @@ function AudioStoryboardPlanPanel({
for (const slot of missing) {
setProductAngleBusy(slot.value)
try {
const references = selectProductAngleReferenceItems(working, slot.value)
const ref = await generateProductAngleAsset(job.id, {
source_ref: working[0].ref,
source_ref: references[0].ref,
source_refs: references.map((item) => item.ref),
source_notes: productAngleSourceNotes(references),
target_view: slot.label,
note: slot.hint,
note: `${slot.hint};请综合这些同产品参考图补目标视角,不要只照抄某一张。`,
})
working = [
...working,
@@ -2117,13 +2169,15 @@ function AudioStoryboardPlanPanel({
const generateMissingProductAngle = async (slot: typeof PRODUCT_VIEW_SLOTS[number]) => {
if (!job || !productItems.length) return
const source = productItems[0]
const references = selectProductAngleReferenceItems(productItems, slot.value)
setProductAngleBusy(slot.value)
try {
const ref = await generateProductAngleAsset(job.id, {
source_ref: source.ref,
source_ref: references[0].ref,
source_refs: references.map((item) => item.ref),
source_notes: productAngleSourceNotes(references),
target_view: slot.label,
note: slot.hint,
note: `${slot.hint};请综合这些同产品参考图补目标视角,不要只照抄某一张。`,
})
setProductItems((prev) => {
const next = [...prev, createProductRefItem(ref, prev.length, "ai", slot.value, `AI 补齐:${slot.hint}`, "white", undefined, undefined, undefined, "", 1)]