auto-save 2026-05-17 19:32 (~4)

This commit is contained in:
2026-05-17 19:32:19 +08:00
parent a9d5962496
commit 96c998c04d
4 changed files with 244 additions and 130 deletions

View File

@@ -1,131 +1,5 @@
{
"entries": [
{
"files_changed": 1,
"hash": "d3ee267",
"message": "auto-save 2026-05-15 10:31 (~1)",
"ts": "2026-05-15T10:31:58+08:00",
"type": "commit"
},
{
"files_changed": 1,
"message": "Codex 会话活跃 · 最近命令codex · 1 项未提交变更 · 最近提交auto-save 2026-05-15 10:31 (~1)",
"ts": "2026-05-15T02:33:08Z",
"type": "session-heartbeat"
},
{
"files_changed": 1,
"hash": "919cf8f",
"message": "auto-save 2026-05-15 10:37 (~1)",
"ts": "2026-05-15T10:37:36+08:00",
"type": "commit"
},
{
"files_changed": 1,
"message": "Codex 会话活跃 · 最近命令codex · 1 项未提交变更 · 最近提交auto-save 2026-05-15 10:37 (~1)",
"ts": "2026-05-15T02:43:08Z",
"type": "session-heartbeat"
},
{
"files_changed": 1,
"hash": "48007a7",
"message": "auto-save 2026-05-15 10:43 (~1)",
"ts": "2026-05-15T10:43:15+08:00",
"type": "commit"
},
{
"files_changed": 1,
"hash": "d58c710",
"message": "auto-save 2026-05-15 10:48 (~1)",
"ts": "2026-05-15T10:48:49+08:00",
"type": "commit"
},
{
"files_changed": 1,
"message": "Codex 会话活跃 · 最近命令codex · 1 项未提交变更 · 最近提交auto-save 2026-05-15 10:48 (~1)",
"ts": "2026-05-15T02:53:08Z",
"type": "session-heartbeat"
},
{
"files_changed": 1,
"hash": "0debed8",
"message": "auto-save 2026-05-15 10:54 (~1)",
"ts": "2026-05-15T10:54:23+08:00",
"type": "commit"
},
{
"files_changed": 1,
"hash": "6b593cc",
"message": "auto-save 2026-05-15 11:00 (~1)",
"ts": "2026-05-15T11:01:11+08:00",
"type": "commit"
},
{
"files_changed": 1,
"message": "Codex 会话活跃 · 最近命令codex · 1 项未提交变更 · 最近提交auto-save 2026-05-15 11:00 (~1)",
"ts": "2026-05-15T03:04:44Z",
"type": "session-heartbeat"
},
{
"files_changed": 1,
"hash": "9e5d853",
"message": "auto-save 2026-05-15 11:07 (~1)",
"ts": "2026-05-15T11:07:11+08:00",
"type": "commit"
},
{
"files_changed": 1,
"hash": "4d66653",
"message": "auto-save 2026-05-15 11:12 (~1)",
"ts": "2026-05-15T11:12:45+08:00",
"type": "commit"
},
{
"files_changed": 1,
"message": "Codex 会话活跃 · 最近命令codex · 1 项未提交变更 · 最近提交auto-save 2026-05-15 11:12 (~1)",
"ts": "2026-05-15T03:14:44Z",
"type": "session-heartbeat"
},
{
"files_changed": 6,
"hash": "08aed2a",
"message": "auto-save 2026-05-15 11:18 (+1, ~4)",
"ts": "2026-05-15T11:18:18+08:00",
"type": "commit"
},
{
"files_changed": 1,
"hash": "e6b2768",
"message": "auto-save 2026-05-15 11:23 (~1)",
"ts": "2026-05-15T11:23:51+08:00",
"type": "commit"
},
{
"files_changed": 1,
"message": "Codex 会话活跃 · 最近命令codex · 1 项未提交变更 · 最近提交auto-save 2026-05-15 11:23 (~1)",
"ts": "2026-05-15T03:24:44Z",
"type": "session-heartbeat"
},
{
"files_changed": 5,
"hash": "55a9a9d",
"message": "auto-save 2026-05-15 11:29 (+1, ~3)",
"ts": "2026-05-15T11:29:23+08:00",
"type": "commit"
},
{
"files_changed": 1,
"message": "Codex 会话活跃 · 最近命令codex · 1 项未提交变更 · 最近提交auto-save 2026-05-15 11:29 (+1, ~3)",
"ts": "2026-05-15T03:34:44Z",
"type": "session-heartbeat"
},
{
"files_changed": 1,
"hash": "967a827",
"message": "auto-save 2026-05-15 11:34 (~1)",
"ts": "2026-05-15T11:34:55+08:00",
"type": "commit"
},
{
"files_changed": 1,
"hash": "7bf9e0f",
@@ -3273,6 +3147,123 @@
"type": "session-heartbeat",
"message": "Codex 会话活跃 · 最近命令codex · 分支 main · 1 项未提交变更 · 最近提交auto-save 2026-05-17 16:43 (~4)",
"files_changed": 1
},
{
"ts": "2026-05-17T16:54:22+08:00",
"type": "commit",
"message": "auto-save 2026-05-17 16:54 (~4)",
"hash": "9798e97",
"files_changed": 4
},
{
"ts": "2026-05-17T16:56:40+08:00",
"type": "commit",
"message": "feat: optimize product pool uploads",
"hash": "84108ee",
"files_changed": 1
},
{
"ts": "2026-05-17T08:58:27Z",
"type": "session-heartbeat",
"message": "Codex 会话活跃 · 最近命令codex · 分支 main · 1 项未提交变更 · 最近提交feat: optimize product pool uploads",
"files_changed": 1
},
{
"ts": "2026-05-17T09:08:27Z",
"type": "session-heartbeat",
"message": "Codex 会话活跃 · 最近命令codex · 分支 main · 1 项未提交变更 · 最近提交feat: optimize product pool uploads",
"files_changed": 1
},
{
"ts": "2026-05-17T09:18:27Z",
"type": "session-heartbeat",
"message": "Codex 会话活跃 · 最近命令codex · 分支 main · 1 项未提交变更 · 最近提交feat: optimize product pool uploads",
"files_changed": 1
},
{
"ts": "2026-05-17T09:28:27Z",
"type": "session-heartbeat",
"message": "Codex 会话活跃 · 最近命令codex · 分支 main · 1 项未提交变更 · 最近提交feat: optimize product pool uploads",
"files_changed": 1
},
{
"ts": "2026-05-17T09:38:27Z",
"type": "session-heartbeat",
"message": "Codex 会话活跃 · 最近命令codex · 分支 main · 1 项未提交变更 · 最近提交feat: optimize product pool uploads",
"files_changed": 1
},
{
"ts": "2026-05-17T09:48:27Z",
"type": "session-heartbeat",
"message": "Codex 会话活跃 · 最近命令codex · 分支 main · 1 项未提交变更 · 最近提交feat: optimize product pool uploads",
"files_changed": 1
},
{
"ts": "2026-05-17T09:58:27Z",
"type": "session-heartbeat",
"message": "Codex 会话活跃 · 最近命令codex · 分支 main · 1 项未提交变更 · 最近提交feat: optimize product pool uploads",
"files_changed": 1
},
{
"ts": "2026-05-17T10:08:27Z",
"type": "session-heartbeat",
"message": "Codex 会话活跃 · 最近命令codex · 分支 main · 1 项未提交变更 · 最近提交feat: optimize product pool uploads",
"files_changed": 1
},
{
"ts": "2026-05-17T10:18:27Z",
"type": "session-heartbeat",
"message": "Codex 会话活跃 · 最近命令codex · 分支 main · 1 项未提交变更 · 最近提交feat: optimize product pool uploads",
"files_changed": 1
},
{
"ts": "2026-05-17T10:28:28Z",
"type": "session-heartbeat",
"message": "Codex 会话活跃 · 最近命令codex · 分支 main · 1 项未提交变更 · 最近提交feat: optimize product pool uploads",
"files_changed": 1
},
{
"ts": "2026-05-17T10:38:28Z",
"type": "session-heartbeat",
"message": "Codex 会话活跃 · 最近命令codex · 分支 main · 1 项未提交变更 · 最近提交feat: optimize product pool uploads",
"files_changed": 1
},
{
"ts": "2026-05-17T10:48:28Z",
"type": "session-heartbeat",
"message": "Codex 会话活跃 · 最近命令codex · 分支 main · 1 项未提交变更 · 最近提交feat: optimize product pool uploads",
"files_changed": 1
},
{
"ts": "2026-05-17T10:58:28Z",
"type": "session-heartbeat",
"message": "Codex 会话活跃 · 最近命令codex · 分支 main · 1 项未提交变更 · 最近提交feat: optimize product pool uploads",
"files_changed": 1
},
{
"ts": "2026-05-17T11:08:28Z",
"type": "session-heartbeat",
"message": "Codex 会话活跃 · 最近命令codex · 分支 main · 1 项未提交变更 · 最近提交feat: optimize product pool uploads",
"files_changed": 1
},
{
"ts": "2026-05-17T11:18:28Z",
"type": "session-heartbeat",
"message": "Codex 会话活跃 · 最近命令codex · 分支 main · 1 项未提交变更 · 最近提交feat: optimize product pool uploads",
"files_changed": 1
},
{
"ts": "2026-05-17T19:24:23+08:00",
"type": "commit",
"message": "fix: tolerate product view model output",
"hash": "a9d5962",
"files_changed": 2
},
{
"ts": "2026-05-17T11:28:28Z",
"type": "session-heartbeat",
"message": "Codex 会话活跃 · 最近命令codex · 分支 main · 1 项未提交变更 · 最近提交fix: tolerate product view model output",
"files_changed": 1
}
]
}

View File

@@ -4274,12 +4274,54 @@ PRODUCT_VIEW_LABELS = {
"back_bottom": "背面/底部",
}
PRODUCT_BACKGROUND_VALUES = ["white", "black", "simple", "complex", "unknown"]
PRODUCT_USE_TAG_VALUES = [
"hero_packshot",
"wearing_scale",
"inner_contact",
"side_thickness",
"asymmetry",
"button_detail",
"back_bottom",
"material_texture",
]
def default_product_use_tags(view: str) -> list[str]:
defaults = {
"front": ["hero_packshot", "asymmetry"],
"left_45": ["hero_packshot", "asymmetry", "button_detail"],
"right_45": ["hero_packshot", "asymmetry", "button_detail"],
"side_thickness": ["side_thickness", "wearing_scale"],
"inner_contacts": ["inner_contact", "wearing_scale"],
"back_bottom": ["back_bottom", "material_texture"],
}
return defaults.get(view, ["hero_packshot"])
def normalize_product_use_tags(tags: object, view: str) -> list[str]:
if isinstance(tags, str):
raw_tags = re.split(r"[,/、\s]+", tags)
elif isinstance(tags, list):
raw_tags = [str(x) for x in tags]
else:
raw_tags = []
result = []
for tag in raw_tags + default_product_use_tags(view):
tag = str(tag).strip()
if tag in PRODUCT_USE_TAG_VALUES and tag not in result:
result.append(tag)
return result[:4]
def fallback_product_view(index: int) -> dict:
view = PRODUCT_VIEW_VALUES[min(index, len(PRODUCT_VIEW_VALUES) - 1)]
return {
"view": view,
"background": "unknown",
"use_tags": default_product_use_tags(view),
"note": f"{PRODUCT_VIEW_LABELS.get(view, view)}参考;模型识别不可用时按上传顺序自动标注,请人工只检查备注。",
"risk": "模型识别不可用,按上传顺序兜底",
"confidence": 0.25,
}
@@ -4300,21 +4342,37 @@ def parse_product_view_response(raw: str, index: int) -> dict:
flags=re.I,
)
confidence_match = re.search(r'["\']?confidence["\']?\s*[:]\s*["\']?([0-9.]+)', text, flags=re.I)
background_match = re.search(r'["\']?background["\']?\s*[:]\s*["\']?([a-z0-9_]+)', text, flags=re.I)
tags_match = re.search(r'["\']?use_tags["\']?\s*[:]\s*\[([\s\S]*?)\]', text, flags=re.I)
risk_match = re.search(
r'["\']?risk["\']?\s*[:]\s*["\']?([\s\S]*?)(?:["\']?\s*[,}]\s*$)',
text,
flags=re.I,
)
data = {
"view": view_match.group(1) if view_match else "",
"background": background_match.group(1) if background_match else "unknown",
"use_tags": re.findall(r"[a-z_]+", tags_match.group(1)) if tags_match else [],
"note": note_match.group(1) if note_match else "",
"risk": risk_match.group(1) if risk_match else "",
"confidence": confidence_match.group(1) if confidence_match else 0.45,
}
view = str(data.get("view") or "").strip().strip('"\' ,。')
if view not in PRODUCT_VIEW_VALUES:
return fallback_product_view(index)
background = str(data.get("background") or "unknown").strip().strip('"\' ,。')
if background not in PRODUCT_BACKGROUND_VALUES:
background = "unknown"
use_tags = normalize_product_use_tags(data.get("use_tags"), view)
note = str(data.get("note") or "").strip().strip('"\' ,,。')
note = re.sub(r"\s+", " ", note)[:220] or f"{PRODUCT_VIEW_LABELS.get(view, view)}参考"
risk = str(data.get("risk") or "").strip().strip('"\' ,,。')
risk = re.sub(r"\s+", " ", risk)[:120]
try:
confidence = max(0.0, min(1.0, float(data.get("confidence", 0.5))))
except Exception:
confidence = 0.5
return {"view": view, "note": note, "confidence": confidence}
return {"view": view, "background": background, "use_tags": use_tags, "note": note, "risk": risk, "confidence": confidence}
def analyze_product_view(ref_path: Path, index: int) -> dict:
@@ -4324,10 +4382,13 @@ def analyze_product_view(ref_path: Path, index: int) -> dict:
prompt = (
"You are inspecting a product reference image for a SKG neck-and-shoulder wearable massage device. The background may be white, black, or simple studio color. "
"Classify the camera/view angle into exactly one enum: front, left_45, right_45, side_thickness, inner_contacts, back_bottom. "
"Also write a concise Chinese note for video generation, focused on visible structure, asymmetry, thickness, inner massage contacts, buttons, opening width, and shoulder-neck wearing scale. "
"Classify background into exactly one enum: white, black, simple, complex, unknown. Do not request or perform background conversion. "
"Add use_tags from this enum only: hero_packshot, wearing_scale, inner_contact, side_thickness, asymmetry, button_detail, back_bottom, material_texture. "
"Also write a concise Chinese note for video generation, focused on visible structure, asymmetry, thickness, inner massage contacts, buttons, opening width, shoulder-neck wearing scale, and what this image is best used for in video generation. "
"Write risk in Chinese only if this reference may mislead video generation, such as cropped product, hand blocking, low detail, strong reflection, or background confusion; otherwise use empty string. "
"If uncertain, choose the closest useful view; do not ask the user. "
"Output one-line strict JSON only. Do not use markdown or line breaks. "
"{\"view\":\"front|left_45|right_45|side_thickness|inner_contacts|back_bottom\", \"note\":\"中文备注\", \"confidence\":0.0}."
"{\"view\":\"front|left_45|right_45|side_thickness|inner_contacts|back_bottom\", \"background\":\"white|black|simple|complex|unknown\", \"use_tags\":[\"hero_packshot\"], \"note\":\"中文备注\", \"risk\":\"\", \"confidence\":0.0}."
)
try:
resp = llm().chat.completions.create(
@@ -4364,7 +4425,10 @@ def analyze_product_views(job_id: str, req: AnalyzeProductViewsReq) -> dict:
items.append({
"index": index,
"view": result["view"],
"background": result.get("background", "unknown"),
"use_tags": result.get("use_tags", default_product_use_tags(result["view"])),
"note": result["note"],
"risk": result.get("risk", ""),
"confidence": result["confidence"],
})
used = {item["view"] for item in items}

View File

@@ -89,7 +89,10 @@ type ProductRefItem = {
id: string
ref: ImageRef
view: string
background: string
useTags: string[]
note: string
risk: string
source: "upload" | "ai"
confidence?: number
}
@@ -105,6 +108,25 @@ const PRODUCT_VIEW_SLOTS = [
const MAX_PRODUCT_REFS_PER_VIDEO = 6
const PRODUCT_BACKGROUND_LABELS: Record<string, string> = {
white: "白底",
black: "黑底",
simple: "纯色/简单",
complex: "复杂背景",
unknown: "背景未知",
}
const PRODUCT_USE_TAG_LABELS: Record<string, string> = {
hero_packshot: "主外观",
wearing_scale: "佩戴比例",
inner_contact: "触点",
side_thickness: "厚度",
asymmetry: "非对称",
button_detail: "按键",
back_bottom: "背底",
material_texture: "材质",
}
const controlClass =
"h-10 rounded-md border border-white/10 bg-black/55 px-3 text-[12px] text-white outline-none transition focus:border-cyan-300/60 disabled:cursor-not-allowed disabled:opacity-40"
@@ -330,12 +352,39 @@ function productViewLabel(view: string) {
return PRODUCT_VIEW_SLOTS.find((slot) => slot.value === view)?.label ?? view
}
function productBackgroundLabel(background: string) {
return PRODUCT_BACKGROUND_LABELS[background] ?? PRODUCT_BACKGROUND_LABELS.unknown
}
function defaultProductUseTags(view: string) {
const defaults: Record<string, string[]> = {
front: ["hero_packshot", "asymmetry"],
left_45: ["hero_packshot", "asymmetry", "button_detail"],
right_45: ["hero_packshot", "asymmetry", "button_detail"],
side_thickness: ["side_thickness", "wearing_scale"],
inner_contacts: ["inner_contact", "wearing_scale"],
back_bottom: ["back_bottom", "material_texture"],
}
return defaults[view] ?? ["hero_packshot"]
}
function normalizeProductUseTags(tags: string[] | undefined, view: string) {
const result: string[] = []
for (const tag of [...(tags ?? []), ...defaultProductUseTags(view)]) {
if (PRODUCT_USE_TAG_LABELS[tag] && !result.includes(tag)) result.push(tag)
}
return result.slice(0, 4)
}
function createProductRefItem(
ref: ImageRef,
index: number,
source: ProductRefItem["source"] = "upload",
view?: string,
note?: string,
background = "unknown",
useTags?: string[],
risk = "",
confidence?: number,
): ProductRefItem {
const slot = PRODUCT_VIEW_SLOTS[index] ?? PRODUCT_VIEW_SLOTS[PRODUCT_VIEW_SLOTS.length - 1]
@@ -344,7 +393,10 @@ function createProductRefItem(
id: productRefKey(ref, index),
ref,
view: view ?? targetSlot.value,
background,
useTags: normalizeProductUseTags(useTags, view ?? targetSlot.value),
note: note ?? targetSlot.hint,
risk,
source,
confidence,
}
@@ -353,7 +405,11 @@ function createProductRefItem(
function productReferenceNotes(items: ProductRefItem[]) {
if (!items.length) return ""
return items
.map((item, index) => `${index + 1}. ${productViewLabel(item.view)}${item.note || "无补充备注"}`)
.map((item, index) => {
const tags = item.useTags.map((tag) => PRODUCT_USE_TAG_LABELS[tag]).filter(Boolean).join("/")
const risk = item.risk ? `;风险:${item.risk}` : ""
return `${index + 1}. ${productViewLabel(item.view)}${productBackgroundLabel(item.background)}${tags}${item.note || "无补充备注"}${risk}`
})
.join("")
}

View File

@@ -166,7 +166,10 @@ export async function generateProductAngleAsset(
export interface ProductViewAnalysisItem {
index: number
view: string
background?: string
use_tags?: string[]
note: string
risk?: string
confidence: number
}