auto-save 2026-05-18 07:05 (~8)
This commit is contained in:
@@ -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)]
|
||||
|
||||
Reference in New Issue
Block a user