auto-save 2026-05-17 19:59 (~3)

This commit is contained in:
2026-05-17 19:59:06 +08:00
parent fc48499319
commit d32e87a376
3 changed files with 84 additions and 37 deletions

View File

@@ -1,32 +1,5 @@
{
"entries": [
{
"files_changed": 3,
"hash": "de3cef4",
"message": "auto-save 2026-05-15 11:51 (~3)",
"ts": "2026-05-15T11:51:33+08:00",
"type": "commit"
},
{
"files_changed": 2,
"message": "Codex 会话活跃 · 最近命令codex · 2 项未提交变更 · 最近提交auto-save 2026-05-15 11:51 (~3)",
"ts": "2026-05-15T03:54:44Z",
"type": "session-heartbeat"
},
{
"files_changed": 2,
"hash": "d98f518",
"message": "auto-save 2026-05-15 11:56 (~2)",
"ts": "2026-05-15T11:57:04+08:00",
"type": "commit"
},
{
"files_changed": 1,
"hash": "5676c9a",
"message": "auto-save 2026-05-15 12:02 (~1)",
"ts": "2026-05-15T12:02:34+08:00",
"type": "commit"
},
{
"files_changed": 1,
"message": "Codex 会话活跃 · 最近命令codex · 1 项未提交变更 · 最近提交auto-save 2026-05-15 12:02 (~1)",
@@ -3264,6 +3237,32 @@
"type": "session-heartbeat",
"message": "Codex 会话活跃 · 最近命令codex · 分支 main · 1 项未提交变更 · 最近提交auto-save 2026-05-17 19:37 (~4)",
"files_changed": 1
},
{
"ts": "2026-05-17T19:48:24+08:00",
"type": "commit",
"message": "auto-save 2026-05-17 19:48 (~4)",
"hash": "9cfb633",
"files_changed": 4
},
{
"ts": "2026-05-17T11:48:28Z",
"type": "session-heartbeat",
"message": "Codex 会话活跃 · 最近命令codex · 分支 main · 1 项未提交变更 · 最近提交auto-save 2026-05-17 19:48 (~4)",
"files_changed": 1
},
{
"ts": "2026-05-17T19:53:03+08:00",
"type": "commit",
"message": "feat: standardize product asset inputs",
"hash": "fc48499",
"files_changed": 3
},
{
"ts": "2026-05-17T11:58:29Z",
"type": "session-heartbeat",
"message": "Codex 会话活跃 · 最近命令codex · 分支 main · 3 项未提交变更 · 最近提交feat: standardize product asset inputs",
"files_changed": 3
}
]
}

View File

@@ -589,7 +589,7 @@
<tr><td><code>web/next.config.mjs</code></td><td>Next.js 构建配置:静态导出、图片不走优化、禁用开发环境左下角 Next Dev Indicator并移除 Next 16 已不支持的 <code>eslint</code> 顶层配置,避免本地 dev 出现配置 Issue 提示。</td></tr>
<tr><td><code>web/app/globals.css</code></td><td>全局主题变量、登录页视觉样式、ReactFlow 样式引用,以及本地开发态 <code>nextjs-portal</code> 遮挡隐藏规则。</td></tr>
<tr><td><code>web/app/page.tsx</code></td><td>产品工作台主状态jobs、activeJobId、生成任务状态主渲染为全屏素材输入列 + 信息流广告复刻工作表;“开始”编排状态只负责在下载完成后自动触发 <code>triggerTranscribe</code>不再默认触发抽帧、Vision 扫描或分镜初稿保存;底部吸附音频条不再从主界面渲染。</td></tr>
<tr><td><code>web/components/ad-recreation-board.tsx</code></td><td>信息流广告复刻工作表:左侧素材输入;右侧展示视频下载状态、默认折叠的音频文案依据,以及统一的音频解析结果面板;面板顶部是一行讲话人/节奏/背景音摘要,下方左侧为原视频播放器、右侧为逐句时间轴,底部横向音频波形用参考图式的连续灰色包络显示响度、停顿和密集爆点。视频播放时通过 <code>requestAnimationFrame</code> 平滑驱动波形播放线,同时同步高亮并滚动当前句;点击音频波形或字幕行会跳转原视频时间。音频结果下方是信息流复刻分镜工作台:顶部产品参考区是“同一产品素材池”,不限量上传产品图,不做不同产品身份判断;上传原图推荐长边 1200-2000px、短边至少 600px但后端会统一生成最长边 1600px、JPEG 92 的 AI 工作副本,并回显尺寸、自动转换和风险标注;上传后自动识别正面/左右 45 度/厚度/内侧触点/背底等视角,并标注背景类型、用途标签、生成风险和备注,用户只检查备注,鼠标悬停可放大预览;缺视角补图失败时保留重试入口。每条音频分镜纵向排列,行内从左到右串起原内容、新口播文案、画面规划/产品融入、参考帧/关键元素和 6 个候选视频槽。单条生成会从产品素材池按分镜角色、视角优先级、用途标签、置信度和风险自动挑选最多 6 张相关产品图,不会把全部产品图提交给生视频模型,然后复用现有生视频接口提交 Seedance 候选。旧分镜卡、抽帧控制和视频生成组件仍保留在文件里,但当前主路径不渲染。</td></tr>
<tr><td><code>web/components/ad-recreation-board.tsx</code></td><td>信息流广告复刻工作表:左侧素材输入;右侧展示视频下载状态、默认折叠的音频文案依据,以及统一的音频解析结果面板;面板顶部是一行讲话人/节奏/背景音摘要,下方左侧为原视频播放器、右侧为逐句时间轴,底部横向音频波形用参考图式的连续灰色包络显示响度、停顿和密集爆点。视频播放时通过 <code>requestAnimationFrame</code> 平滑驱动波形播放线,同时同步高亮并滚动当前句;点击音频波形或字幕行会跳转原视频时间。音频结果下方是信息流复刻分镜工作台:顶部产品参考区是“同一产品素材池”,不限量上传产品图,不做不同产品身份判断;上传原图推荐长边 1200-2000px、短边至少 600px但后端会统一生成最长边 1600px、JPEG 92 的 AI 工作副本,并回显尺寸、自动转换和风险标注;上传后自动识别正面/左右 45 度/厚度/内侧触点/背底等视角,并标注背景类型、用途标签、生成风险和备注,用户只检查备注,鼠标悬停通过固定浮层显示大图预览,能盖过滚动容器和分镜框架;缺视角补图失败时保留重试入口。每条音频分镜纵向排列,行内从左到右串起原内容、新口播文案、画面规划/产品融入、参考帧/关键元素和 6 个候选视频槽。单条生成会从产品素材池按分镜角色、视角优先级、用途标签、置信度和风险自动挑选最多 6 张相关产品图,不会把全部产品图提交给生视频模型,然后复用现有生视频接口提交 Seedance 候选。旧分镜卡、抽帧控制和视频生成组件仍保留在文件里,但当前主路径不渲染。</td></tr>
<tr><td><code>web/app/login/page.tsx</code></td><td>生产登录页:访问账号/访问密钥表单、保持登录、错误/成功状态;当前只在原版 Digital Oasis 动态背景上叠加一个组合登录框,桌面端左侧是动态角色,右侧是图标化登录表单;面板左上角展示官网 SKG 字标和中文“营销内容工作台”系统标识。</td></tr>
<tr><td><code>web/app/login/layout.tsx</code></td><td>登录路由专属 layout覆盖全站默认网页标题和描述为空避免 <code>/login</code> 继承工作台 metadata 后在页面源码里继续出现登录界面文字以外的文案。</td></tr>
<tr><td><code>web/components/login/oasis-canvas.tsx</code></td><td>登录页全屏动态视觉层:用 iframe 直接承载下载包 <code>web/public/oasis-source/index.html</code> 的原 WebGPU / Three.js 草场源码;父级登录页只覆盖自己的文案和表单,并在捕获阶段把全局鼠标坐标同时用原生事件和 <code>postMessage</code> 转发给 iframe避免登录面板或输入框遮挡时草地失去鼠标响应。</td></tr>
@@ -964,6 +964,18 @@ SubjectAsset {
<h2>变更记录</h2>
<p>这个记录不是 git log 的替代品。它记录“产品理解发生了什么变化、影响了哪些源码、你以后描述需求时该怎么说”。后续每次改功能都要补一条。</p>
<div class="changelog">
<article class="change">
<header>
<h3>2026-05-17 · 产品图悬停预览改为大尺寸顶层浮层</h3>
<span class="tag rose">UI</span>
<span class="tag cyan">Workflow</span>
</header>
<div class="body">
<p><strong>问题:</strong>产品素材池底部图片悬停时,放大预览仍在滚动容器内部,容易被产品区或分镜框架裁掉;预览尺寸也偏小,不利于检查肩颈产品厚度、触点和左右差异。</p>
<p><strong>改动:</strong><code>ProductReferenceCard</code> 将悬停预览从卡片内绝对定位改为 <code>createPortal(document.body)</code> 渲染的固定浮层,宽度提升到最多 380px并根据鼠标位置自动避开视口底部和右侧。</p>
<p><strong>影响:</strong><code>web/components/ad-recreation-board.tsx</code><code>docs/source-analysis.html</code>。后续产品图检查应继续使用该顶层预览,避免把预览重新放回带 <code>overflow</code> 的滚动容器里。</p>
</div>
</article>
<article class="change">
<header>
<h3>2026-05-17 · 产品图上传统一生成 AI 工作副本</h3>

View File

@@ -1,6 +1,7 @@
"use client"
import { type ReactNode, type RefObject, useEffect, useMemo, useRef, useState } from "react"
import { type MouseEvent as ReactMouseEvent, type ReactNode, type RefObject, useEffect, useMemo, useRef, useState } from "react"
import { createPortal } from "react-dom"
import {
AlertTriangle, Check, ChevronDown, Circle, Film, FileText, Image as ImageIcon, Link2, Loader2,
Mic, Package, PanelRight, Play, Plus, Scissors, Sparkles, Trash2, Upload, Wand2,
@@ -1515,20 +1516,55 @@ function ProductReferenceCard({
const tagLabels = item.useTags.map((tag) => PRODUCT_USE_TAG_LABELS[tag]).filter(Boolean)
const assetWarnings = item.assetMeta?.warnings ?? []
const assetActions = item.assetMeta?.actions ?? []
return (
<div className="grid min-w-0 grid-cols-[74px_minmax(0,1fr)_28px] gap-2 rounded-md border border-white/10 bg-black/26 p-2">
<div className="group relative h-[74px] w-[74px] overflow-visible rounded-md border border-white/10 bg-white">
<img src={src} alt={productViewLabel(item.view)} className="h-full w-full rounded-md object-contain" />
<div className="pointer-events-none absolute left-0 top-[82px] z-50 hidden w-60 rounded-lg border border-white/15 bg-black/90 p-2 shadow-2xl group-hover:block">
<img src={src} alt="" className="aspect-square w-full rounded-md bg-white object-contain" />
<div className="mt-1 text-[11px] leading-snug text-white/62">
const [previewPos, setPreviewPos] = useState<{ left: number; top: number } | null>(null)
function updatePreviewPosition(event: ReactMouseEvent<HTMLDivElement>) {
const margin = 16
const previewWidth = Math.min(380, window.innerWidth - margin * 2)
const previewHeight = previewWidth + 118
let left = event.clientX + 18
let top = event.clientY + 18
if (left + previewWidth > window.innerWidth - margin) {
left = event.clientX - previewWidth - 18
}
if (top + previewHeight > window.innerHeight - margin) {
top = window.innerHeight - previewHeight - margin
}
setPreviewPos({
left: Math.max(margin, left),
top: Math.max(margin, top),
})
}
const preview = previewPos && typeof document !== "undefined"
? createPortal(
<div
className="pointer-events-none fixed z-[9999] w-[min(380px,calc(100vw-32px))] rounded-xl border border-white/15 bg-black/94 p-3 shadow-[0_28px_80px_rgba(0,0,0,0.72)]"
style={{ left: previewPos.left, top: previewPos.top }}
>
<img src={src} alt="" className="aspect-square w-full rounded-lg bg-white object-contain" />
<div className="mt-2 text-[11px] leading-snug text-white/68">
{productViewLabel(item.view)} · {productBackgroundLabel(item.background)} · {tagLabels.join(" / ")}
<br />
{item.note}
{item.risk ? <><br />{item.risk}</> : null}
{assetWarnings.length ? <><br />{assetWarnings.join("")}</> : null}
</div>
</div>
</div>,
document.body,
)
: null
return (
<div className="grid min-w-0 grid-cols-[74px_minmax(0,1fr)_28px] gap-2 rounded-md border border-white/10 bg-black/26 p-2">
<div
className="relative h-[74px] w-[74px] rounded-md border border-white/10 bg-white"
onMouseEnter={updatePreviewPosition}
onMouseMove={updatePreviewPosition}
onMouseLeave={() => setPreviewPos(null)}
>
<img src={src} alt={productViewLabel(item.view)} className="h-full w-full rounded-md object-contain" />
{preview}
<span className="absolute left-1 top-1 rounded bg-black/70 px-1 text-[9px] text-white/75">{item.source === "ai" ? "AI" : "图"}</span>
</div>
<div className="min-w-0">