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": [ "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, "files_changed": 1,
"message": "Codex 会话活跃 · 最近命令codex · 1 项未提交变更 · 最近提交auto-save 2026-05-15 12:02 (~1)", "message": "Codex 会话活跃 · 最近命令codex · 1 项未提交变更 · 最近提交auto-save 2026-05-15 12:02 (~1)",
@@ -3264,6 +3237,32 @@
"type": "session-heartbeat", "type": "session-heartbeat",
"message": "Codex 会话活跃 · 最近命令codex · 分支 main · 1 项未提交变更 · 最近提交auto-save 2026-05-17 19:37 (~4)", "message": "Codex 会话活跃 · 最近命令codex · 分支 main · 1 项未提交变更 · 最近提交auto-save 2026-05-17 19:37 (~4)",
"files_changed": 1 "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/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/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/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/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/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> <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> <h2>变更记录</h2>
<p>这个记录不是 git log 的替代品。它记录“产品理解发生了什么变化、影响了哪些源码、你以后描述需求时该怎么说”。后续每次改功能都要补一条。</p> <p>这个记录不是 git log 的替代品。它记录“产品理解发生了什么变化、影响了哪些源码、你以后描述需求时该怎么说”。后续每次改功能都要补一条。</p>
<div class="changelog"> <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"> <article class="change">
<header> <header>
<h3>2026-05-17 · 产品图上传统一生成 AI 工作副本</h3> <h3>2026-05-17 · 产品图上传统一生成 AI 工作副本</h3>

View File

@@ -1,6 +1,7 @@
"use client" "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 { import {
AlertTriangle, Check, ChevronDown, Circle, Film, FileText, Image as ImageIcon, Link2, Loader2, AlertTriangle, Check, ChevronDown, Circle, Film, FileText, Image as ImageIcon, Link2, Loader2,
Mic, Package, PanelRight, Play, Plus, Scissors, Sparkles, Trash2, Upload, Wand2, 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 tagLabels = item.useTags.map((tag) => PRODUCT_USE_TAG_LABELS[tag]).filter(Boolean)
const assetWarnings = item.assetMeta?.warnings ?? [] const assetWarnings = item.assetMeta?.warnings ?? []
const assetActions = item.assetMeta?.actions ?? [] const assetActions = item.assetMeta?.actions ?? []
return ( const [previewPos, setPreviewPos] = useState<{ left: number; top: number } | null>(null)
<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"> function updatePreviewPosition(event: ReactMouseEvent<HTMLDivElement>) {
<img src={src} alt={productViewLabel(item.view)} className="h-full w-full rounded-md object-contain" /> const margin = 16
<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"> const previewWidth = Math.min(380, window.innerWidth - margin * 2)
<img src={src} alt="" className="aspect-square w-full rounded-md bg-white object-contain" /> const previewHeight = previewWidth + 118
<div className="mt-1 text-[11px] leading-snug text-white/62"> 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(" / ")} {productViewLabel(item.view)} · {productBackgroundLabel(item.background)} · {tagLabels.join(" / ")}
<br /> <br />
{item.note} {item.note}
{item.risk ? <><br />{item.risk}</> : null} {item.risk ? <><br />{item.risk}</> : null}
{assetWarnings.length ? <><br />{assetWarnings.join("")}</> : null} {assetWarnings.length ? <><br />{assetWarnings.join("")}</> : null}
</div> </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> <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>
<div className="min-w-0"> <div className="min-w-0">