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

-
-

-
+ const [previewPos, setPreviewPos] = useState<{ left: number; top: number } | null>(null)
+
+ function updatePreviewPosition(event: ReactMouseEvent
) {
+ 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(
+
+

+
{productViewLabel(item.view)} · {productBackgroundLabel(item.background)} · {tagLabels.join(" / ")}
{item.note}
{item.risk ? <>
风险:{item.risk}> : null}
{assetWarnings.length ? <>
规格:{assetWarnings.join(";")}> : null}
-
+ ,
+ document.body,
+ )
+ : null
+
+ return (
+
+
setPreviewPos(null)}
+ >
+

+ {preview}
{item.source === "ai" ? "AI" : "图"}