auto-save 2026-05-17 19:59 (~3)
This commit is contained in:
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user